Compare commits

..

18 Commits

Author SHA1 Message Date
ccdd7730b2 Investment
Add:
- utils/pickFile: pilih extention file sesuai kebutuhan
- utils/formatCurrencyDisplay.ts: tampillan uang 2.500
- api-client/api-investment.ts
- api-storage.ts: api strogre wibudev

Fix:
- Integrasi API pada: Create, Edit, Tampilan status & detail
- Button status dan hapus data juga sudah terintegrasi

### No Issue
2025-09-29 17:42:25 +08:00
a474aebb94 Forum:
Fix: link dari avatar ke forumku & penambahan tombol create di forumku jika id forum milik user yang sama seperti user login

### No issue
2025-09-29 10:28:53 +08:00
29b65aeebf Forum
Fix:
- Integrasi API ke semua tampilan

### No Issue
2025-09-26 17:43:50 +08:00
18beb09b42 Forum
Fix:
- Tampilan beranda forum & bisa melakukan search dan sudah terintegrasi API
- Fitur hapus edit dan ubah status sudah terintegrasi API
- List komentar sudah bisa muncul dan bisa mengahpus

### No Issue
2025-09-26 14:46:29 +08:00
54af104f8a Forum
Add:
- api-client/api-forum

Fix:
- Integrasi API: create dan beranda file

### No Issue
2025-09-24 17:30:06 +08:00
8c5602b809 Collaboration
Add:
- Collaboration/GroupChatSection.tsx : fitur room chat

Fix:
- Clear code: Hapus console pada beberapa file

### No Issue
2025-09-24 15:28:16 +08:00
99f058a92f Collaboration
Add:
- (user)/collaboration/[id]/select-of-participants

Fix:
- Integrasi ke api di bagian beranda , partisipan dan group

### No Issue
2025-09-23 17:41:03 +08:00
821a211f58 Collaboration
Fix:
- Integrasi API: Beranda, create, list partisipan, check sudah berpartisipasi ?

### No Issue
2025-09-22 17:31:40 +08:00
333b1d2512 Voting
Fix: Semua tampilan sudah terintegrasi API

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

### No Issue
2025-09-18 17:35:18 +08:00
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
b6d4c0e6a6 API Event
Add:
- api-client/api-event : fetch get one, update data, update status, create event

Fix:
- UI : create , detail dan status: untuk menyambungkan ke API

### No Issue
2025-09-11 17:34:08 +08:00
117 changed files with 8482 additions and 20250 deletions

View File

@@ -26,6 +26,7 @@ export default {
}, },
edgeToEdgeEnabled: true, edgeToEdgeEnabled: true,
package: 'com.bip.hipmimobileapp', package: 'com.bip.hipmimobileapp',
// softwareKeyboardLayoutMode: 'resize', // option: untuk mengatur keyboard pada room chst collaboration
}, },
web: { web: {
@@ -36,6 +37,7 @@ export default {
plugins: [ plugins: [
'expo-router', 'expo-router',
'expo-web-browser',
[ [
'expo-splash-screen', 'expo-splash-screen',
{ {
@@ -67,5 +69,6 @@ export default {
}, },
// Tambahkan environment variables ke sini // Tambahkan environment variables ke sini
API_BASE_URL: process.env.API_BASE_URL, API_BASE_URL: process.env.API_BASE_URL,
BASE_URL: process.env.BASE_URL,
}, },
}; };

View File

@@ -118,14 +118,13 @@ export default function UserLayout() {
headerLeft: () => <BackButton />, headerLeft: () => <BackButton />,
}} }}
/> />
<Stack.Screen {/* <Stack.Screen
name="collaboration/[id]/detail-participant" name="collaboration/[id]/detail-participant"
options={{ options={{
title: "Partisipasi Proyek", title: "Partisipasi Proyek",
headerLeft: () => <BackButton />, headerLeft: () => <BackButton />,
}} }}
/> /> */}
<Stack.Screen <Stack.Screen
name="collaboration/[id]/edit" name="collaboration/[id]/edit"
options={{ options={{
@@ -133,6 +132,20 @@ export default function UserLayout() {
headerLeft: () => <BackButton />, headerLeft: () => <BackButton />,
}} }}
/> />
<Stack.Screen
name="collaboration/[id]/create-pacticipants"
options={{
title: "Ajukan Partisipasi",
headerLeft: () => <BackButton />,
}}
/>
<Stack.Screen
name="collaboration/[id]/select-of-participants"
options={{
title: "Pilih Partisipan",
headerLeft: () => <BackButton />,
}}
/>
{/* ========== End Collaboration Section ========= */} {/* ========== End Collaboration Section ========= */}
@@ -509,6 +522,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,38 +1,96 @@
import { BaseBox, Grid, TextCustom } from "@/components"; /* eslint-disable @typescript-eslint/no-unused-vars */
/* eslint-disable react-hooks/exhaustive-deps */
import {
BaseBox,
Grid,
LoaderCustom,
StackCustom,
TextCustom,
} from "@/components";
import ViewWrapper from "@/components/_ShareComponent/ViewWrapper"; import ViewWrapper from "@/components/_ShareComponent/ViewWrapper";
import { MainColor } from "@/constants/color-palet"; import { MainColor } from "@/constants/color-palet";
import { useAuth } from "@/hooks/use-auth";
import { apiCollaborationGetAll } from "@/service/api-client/api-collaboration";
import { Feather } from "@expo/vector-icons"; import { Feather } from "@expo/vector-icons";
import { useFocusEffect } from "expo-router";
import _ from "lodash";
import { useState, useCallback } from "react";
export default function CollaborationGroup() { export default function CollaborationGroup() {
const { user } = useAuth();
const [listData, setListData] = useState<any[]>();
const [loadingGetData, setLoadingGetData] = useState(false);
useFocusEffect(
useCallback(() => {
onLoadData();
}, [user?.id])
);
const onLoadData = async () => {
try {
setLoadingGetData(true);
const response = await apiCollaborationGetAll({
category: "group",
authorId: user?.id,
});
if (response.success) {
setListData(response.data);
}
} catch (error) {
console.log("[ERROR]", error);
} finally {
setLoadingGetData(false);
}
};
return ( return (
<ViewWrapper hideFooter> <ViewWrapper hideFooter>
{Array.from({ length: 10 }).map((_, index) => ( {loadingGetData ? (
<BaseBox <LoaderCustom />
key={index} ) : _.isEmpty(listData) ? (
paddingBlock={5} <TextCustom align="center">Tidak ada data</TextCustom>
href={`/collaboration/${index}/${generateProjectName()}/room-chat`} ) : (
> <StackCustom>
<Grid> {listData?.map((item: any, index: any) => (
<Grid.Col span={10}> <BaseBox
<TextCustom bold>{generateProjectName()}</TextCustom> key={index}
<TextCustom size="small">2 Anggota</TextCustom> paddingBlock={5}
</Grid.Col> href={`/collaboration/${item?.ProjectCollaboration_RoomChat?.id}/${item?.ProjectCollaboration_RoomChat?.name}/room-chat`}
<Grid.Col
span={2}
style={{ alignItems: "flex-end", justifyContent: "center" }}
> >
<Feather name="chevron-right" size={20} color={MainColor.white} /> <Grid>
</Grid.Col> <Grid.Col span={10}>
</Grid> <TextCustom bold>
</BaseBox> {item?.ProjectCollaboration_RoomChat?.name}
))} </TextCustom>
<TextCustom size="small">
{
item?.ProjectCollaboration_RoomChat
?.ProjectCollaboration_AnggotaRoomChat?.length
}{" "}
Anggota
</TextCustom>
</Grid.Col>
<Grid.Col
span={2}
style={{ alignItems: "flex-end", justifyContent: "center" }}
>
<Feather
name="chevron-right"
size={20}
color={MainColor.white}
/>
</Grid.Col>
</Grid>
</BaseBox>
))}
</StackCustom>
)}
</ViewWrapper> </ViewWrapper>
); );
} }
function generateProjectName() { function generateProjectName() {
const adjectives = [ const adjectives = [
"Blue", "Blue",
@@ -65,4 +123,4 @@ function generateProjectName() {
const randomNoun = nouns[Math.floor(Math.random() * nouns.length)]; const randomNoun = nouns[Math.floor(Math.random() * nouns.length)];
return randomAdjective + randomNoun; return randomAdjective + randomNoun;
} }

View File

@@ -1,8 +1,40 @@
import { FloatingButton, ViewWrapper } from "@/components"; import {
FloatingButton,
LoaderCustom,
TextCustom,
ViewWrapper,
} from "@/components";
import Collaboration_BoxPublishSection from "@/screens/Collaboration/BoxPublishSection"; import Collaboration_BoxPublishSection from "@/screens/Collaboration/BoxPublishSection";
import { router } from "expo-router"; import { apiCollaborationGetAll } from "@/service/api-client/api-collaboration";
import { router, useFocusEffect } from "expo-router";
import _ from "lodash";
import { useCallback, useState } from "react";
export default function CollaborationBeranda() { export default function CollaborationBeranda() {
const [listData, setListData] = useState<any[]>();
const [loadingGetData, setLoadingGetData] = useState(false);
useFocusEffect(
useCallback(() => {
onLoadData();
}, [])
);
const onLoadData = async () => {
try {
setLoadingGetData(true);
const response = await apiCollaborationGetAll({
category: "beranda",
});
setListData(response.data);
} catch (error) {
console.log("[ERROR]", error);
} finally {
setLoadingGetData(false);
}
};
return ( return (
<> <>
<ViewWrapper <ViewWrapper
@@ -15,13 +47,19 @@ export default function CollaborationBeranda() {
/> />
} }
> >
{Array.from({ length: 10 }).map((_, index) => ( {loadingGetData ? (
<Collaboration_BoxPublishSection <LoaderCustom />
key={index} ) : _.isEmpty(listData) ? (
id={index.toString()} <TextCustom align="center">Tidak ada data</TextCustom>
href={`/collaboration/${index}`} ) : (
/> listData?.map((item: any, index: number) => (
))} <Collaboration_BoxPublishSection
key={index}
href={`/collaboration/${item.id}`}
data={item}
/>
))
)}
</ViewWrapper> </ViewWrapper>
</> </>
); );

View File

@@ -1,15 +1,46 @@
import { ButtonCustom, Spacing } 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 Collaboration_BoxPublishSection from "@/screens/Collaboration/BoxPublishSection"; import Collaboration_BoxPublishSection from "@/screens/Collaboration/BoxPublishSection";
import { useState } from "react"; import { apiCollaborationGetAll } from "@/service/api-client/api-collaboration";
import { useFocusEffect } from "expo-router";
import _ from "lodash";
import { useCallback, useState } from "react";
import { View } from "react-native"; import { View } from "react-native";
export default function CollaborationParticipans() { export default function CollaborationParticipans() {
const [activeCategory, setActiveCategory] = useState<string | null>( const [activeCategory, setActiveCategory] = useState<
"participant" "participant" | "my-project"
>("participant");
const { user } = useAuth();
const [listData, setListData] = useState<any[]>();
const [loadingGetData, setLoadingGetData] = useState(false);
useFocusEffect(
useCallback(() => {
onLoadData();
}, [activeCategory])
); );
const onLoadData = async () => {
try {
setLoadingGetData(true);
const response = await apiCollaborationGetAll({
category:
activeCategory === "participant" ? "participant" : "my-project",
authorId: user?.id,
});
setListData(response.data);
} catch (error) {
console.log("[ERROR]", error);
} finally {
setLoadingGetData(false);
}
};
const handlePress = (item: any) => { const handlePress = (item: any) => {
setActiveCategory(item); setActiveCategory(item);
// tambahkan logika lain seperti filter dsb. // tambahkan logika lain seperti filter dsb.
@@ -41,13 +72,13 @@ export default function CollaborationParticipans() {
<Spacing width={"2%"} /> <Spacing width={"2%"} />
<ButtonCustom <ButtonCustom
backgroundColor={ backgroundColor={
activeCategory === "main" ? MainColor.yellow : AccentColor.blue activeCategory === "my-project" ? MainColor.yellow : AccentColor.blue
} }
textColor={ textColor={
activeCategory === "main" ? MainColor.black : MainColor.white activeCategory === "my-project" ? MainColor.black : MainColor.white
} }
style={{ width: "49%" }} style={{ width: "49%" }}
onPress={() => handlePress("main")} onPress={() => handlePress("my-project")}
> >
Proyek Saya Proyek Saya
</ButtonCustom> </ButtonCustom>
@@ -56,22 +87,27 @@ export default function CollaborationParticipans() {
return ( return (
<ViewWrapper hideFooter headerComponent={headerComponent}> <ViewWrapper hideFooter headerComponent={headerComponent}>
{Array.from({ length: 10 }).map((_, index) => ( {loadingGetData ? (
<Collaboration_BoxPublishSection <LoaderCustom />
key={index.toString()} ) : _.isEmpty(listData) ? (
id={index.toString()} <TextCustom align="center">Tidak ada data</TextCustom>
username={` ${ ) : activeCategory === "participant" ? (
activeCategory === "participant" listData?.map((item: any, index: number) => (
? "Partisipasi Proyek" <Collaboration_BoxPublishSection
: "Proyek Saya" key={index.toString()}
}`} data={item?.ProjectCollaboration}
href={ href={`/collaboration/${item?.ProjectCollaboration?.id}/detail-participant`}
activeCategory === "participant" />
? `/collaboration/${index}/detail-participant` ))
: `/collaboration/${index}/detail-project-main` ) : (
} listData?.map((item: any, index: number) => (
/> <Collaboration_BoxPublishSection
))} key={index.toString()}
data={item}
href={`/collaboration/${item?.id}/detail-project-main`}
/>
))
)}
</ViewWrapper> </ViewWrapper>
); );
} }

View File

@@ -1,17 +1,41 @@
/* eslint-disable react-hooks/exhaustive-deps */
/* eslint-disable @typescript-eslint/no-unused-vars */ /* eslint-disable @typescript-eslint/no-unused-vars */
import { import {
AvatarUsernameAndOtherComponent, AvatarUsernameAndOtherComponent,
BackButton, BackButton,
BaseBox,
BoxWithHeaderSection, BoxWithHeaderSection,
Grid, Grid,
StackCustom, StackCustom,
TextCustom, TextCustom,
ViewWrapper, ViewWrapper,
} from "@/components"; } from "@/components";
import { Stack, useLocalSearchParams } from "expo-router"; import { apiCollaborationGroup } from "@/service/api-client/api-collaboration";
import { Stack, useFocusEffect, useLocalSearchParams } from "expo-router";
import { useState, useCallback } from "react";
export default function CollaborationRoomInfo() { export default function CollaborationRoomInfo() {
const { id, detail } = useLocalSearchParams(); const { id, detail } = useLocalSearchParams();
const [data, setData] = useState<any>();
useFocusEffect(
useCallback(() => {
onLoadData();
}, [id])
);
const onLoadData = async () => {
try {
const response = await apiCollaborationGroup({ id: id as string });
if (response.success) {
setData(response.data);
}
} catch (error) {
console.log("[ERROR]", error);
}
};
return ( return (
<> <>
<Stack.Screen <Stack.Screen
@@ -24,7 +48,7 @@ export default function CollaborationRoomInfo() {
<ViewWrapper> <ViewWrapper>
<BoxWithHeaderSection> <BoxWithHeaderSection>
<StackCustom> <StackCustom>
{listData.map((item, index) => ( {listData({ data }).map((item, index) => (
<Grid key={index}> <Grid key={index}>
<Grid.Col span={4}> <Grid.Col span={4}>
<TextCustom bold>{item.title}</TextCustom> <TextCustom bold>{item.title}</TextCustom>
@@ -37,37 +61,42 @@ export default function CollaborationRoomInfo() {
</StackCustom> </StackCustom>
</BoxWithHeaderSection> </BoxWithHeaderSection>
<BoxWithHeaderSection> <BaseBox>
{Array.from({ length: 10 }).map((_, index) => ( <StackCustom gap={10}>
<AvatarUsernameAndOtherComponent key={index} avatarHref={`/profile/${index}`} /> {data?.ProjectCollaboration_AnggotaRoomChat?.map(
))} (item: any, index: number) => (
</BoxWithHeaderSection> <AvatarUsernameAndOtherComponent
key={index}
avatarHref={`/profile/${item?.User?.Profile?.id}`}
name={item?.User?.username}
avatar={item?.User?.Profile?.imageId}
/>
)
)}
</StackCustom>
</BaseBox>
</ViewWrapper> </ViewWrapper>
</> </>
); );
} }
const listData = [ const listData = ({ data }: { data: any }) => [
{ {
title: "Judul Proyek", title: "Judul Proyek",
value: "Judul Proyek", value: data?.ProjectCollaboration?.title || "-",
}, },
{ {
title: "Industri", title: "Industri",
value: "Pilihan Industri", value:
}, data?.ProjectCollaboration?.ProjectCollaborationMaster_Industri?.name ||
{ "-",
title: "Deskripsi",
value: "Deskripsi Proyek",
}, },
{ {
title: "Tujuan Proyek", title: "Tujuan Proyek",
value: value: data?.ProjectCollaboration?.purpose || "-",
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
}, },
{ {
title: "Keuntungan Proyek", title: "Keuntungan Proyek",
value: value: data?.ProjectCollaboration?.benefit || "-",
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
}, },
]; ];

View File

@@ -1,68 +1,13 @@
import { import { BackButton } from "@/components";
BackButton, import { MainColor } from "@/constants/color-palet";
BoxButtonOnFooter,
Grid,
TextCustom,
TextInputCustom,
ViewWrapper,
} from "@/components";
import { AccentColor, MainColor } from "@/constants/color-palet";
import { ICON_SIZE_SMALL } from "@/constants/constans-value"; import { ICON_SIZE_SMALL } from "@/constants/constans-value";
import ChatScreen from "@/screens/Collaboration/GroupChatSection";
import { Feather } from "@expo/vector-icons"; import { Feather } from "@expo/vector-icons";
import { router, Stack, useLocalSearchParams } from "expo-router"; import { router, Stack, useLocalSearchParams } from "expo-router";
import { StyleSheet, TouchableOpacity, View } from "react-native";
export default function CollaborationRoomChat() { export default function CollaborationRoomChat() {
const { id, detail } = useLocalSearchParams(); const { id, detail } = useLocalSearchParams();
const inputChat = () => {
return (
<>
<BoxButtonOnFooter>
{/* <View style={{flexDirection: 'row', alignItems: 'center'}}>
<TextInputCustom placeholder="Ketik pesan..." />
<TouchableOpacity
activeOpacity={0.5}
onPress={() => console.log("Send")}
style={{
backgroundColor: AccentColor.blue,
padding: 10,
borderRadius: 50,
}}
>
<Feather name="send" size={30} color={MainColor.white} />
</TouchableOpacity>
</View> */}
<Grid>
<Grid.Col span={9}>
<TextInputCustom placeholder="Ketik pesan..." />
</Grid.Col>
<Grid.Col span={1}>
<View />
</Grid.Col>
<Grid.Col span={2} style={{ alignItems: "center" }}>
<TouchableOpacity
activeOpacity={0.5}
onPress={() => console.log("Send")}
style={{
backgroundColor: AccentColor.blue,
padding: 10,
borderRadius: 50,
}}
>
<Feather
name="send"
size={30}
color={MainColor.white}
/>
</TouchableOpacity>
</Grid.Col>
</Grid>
</BoxButtonOnFooter>
</>
);
};
return ( return (
<> <>
<Stack.Screen <Stack.Screen
@@ -79,114 +24,8 @@ export default function CollaborationRoomChat() {
), ),
}} }}
/> />
<ViewWrapper footerComponent={inputChat()}>
{dummyData.map((item, index) => ( <ChatScreen id={id as string} />
<View
key={index}
style={[
styles.messageRow,
item.role === 1 ? styles.rightAlign : styles.leftAlign,
]}
>
<View
style={[
styles.bubble,
item.role === 1 ? styles.bubbleRight : styles.bubbleLeft,
]}
>
<TextCustom style={styles.sender}>{item.nama}</TextCustom>
<TextCustom style={styles.message}>{item.chat}</TextCustom>
<TextCustom style={styles.time}>
{new Date(item.time).toLocaleTimeString([], {
hour: "2-digit",
minute: "2-digit",
})}
</TextCustom>
</View>
</View>
))}
{/* <TextInputCustom placeholder="Ketik pesan..." />
<Spacing/> */}
</ViewWrapper>
</> </>
); );
} }
const dummyData = [
{
nama: "Dina",
role: 1,
chat: "Hai! Kamu udah lihat dokumen proyek yang baru?",
time: "2025-07-24T09:01:15Z",
},
{
nama: "Rafi",
role: 2,
chat: "Halo! Iya, aku baru aja baca. Kayaknya kita harus revisi bagian akhir deh.",
time: "2025-07-24T09:02:03Z",
},
{
nama: "Dina",
role: 1,
chat: "Setuju. Aku juga kurang sreg sama penutupnya.",
time: "2025-07-24T09:02:45Z",
},
{
nama: "Rafi",
role: 2,
chat: "Oke, aku coba edit malam ini ya. Nanti aku share ulang versinya.",
time: "2025-07-24T09:03:10Z",
},
{
nama: "Dina",
role: 1,
chat: "Siap, makasih ya. Jangan begadang!",
time: "2025-07-24T09:03:30Z",
},
];
const styles = StyleSheet.create({
container: {
paddingVertical: 10,
paddingHorizontal: 12,
},
messageRow: {
flexDirection: "row",
marginBottom: 12,
},
rightAlign: {
justifyContent: "flex-end",
},
leftAlign: {
justifyContent: "flex-start",
},
bubble: {
maxWidth: "75%",
padding: 10,
borderRadius: 12,
},
bubbleRight: {
backgroundColor: "#DCF8C6", // hijau muda
borderTopRightRadius: 0,
},
bubbleLeft: {
backgroundColor: "#F0F0F0", // abu-abu terang
borderTopLeftRadius: 0,
},
sender: {
fontSize: 12,
fontWeight: "bold",
marginBottom: 2,
color: "#555",
},
message: {
fontSize: 15,
color: "#000",
},
time: {
fontSize: 10,
color: "#888",
textAlign: "right",
marginTop: 4,
},
});

View File

@@ -0,0 +1,80 @@
import {
AlertDefaultSystem,
ButtonCustom,
TextAreaCustom,
ViewWrapper,
} from "@/components";
import { useAuth } from "@/hooks/use-auth";
import { apiCollaborationCreatePartisipasi } from "@/service/api-client/api-collaboration";
import { router, useLocalSearchParams } from "expo-router";
import { useState } from "react";
import Toast from "react-native-toast-message";
export default function CollaborationCreatePartisipans() {
const { user } = useAuth();
const { id } = useLocalSearchParams();
const [description, setDescription] = useState("");
const [isLoading, setLoading] = useState(false);
const handlerSubmitParticipans = async () => {
try {
setLoading(true);
const response = await apiCollaborationCreatePartisipasi({
id: id as string,
data: {
authorId: user?.id,
description,
},
});
if (response.success) {
Toast.show({
type: "success",
text1: "Data berhasil disimpan",
});
router.replace(`/collaboration/${id}/list-of-participants`);
} else {
Toast.show({
type: "error",
text1: "Gagal menyimpan data",
});
}
} catch (error) {
console.log("[ERROR]", error);
} finally {
setLoading(false);
}
};
return (
<ViewWrapper>
<TextAreaCustom
// label="Deskripsi"
placeholder="Masukan deskripsi diri anda .."
value={description}
onChangeText={setDescription}
required
showCount
maxLength={1000}
/>
<ButtonCustom
disabled={description.length === 0}
isLoading={isLoading}
onPress={() => {
AlertDefaultSystem({
title: "Simpan data deskripsi",
message: "Apakah anda sudah yakin ingin menyimpan data ini ?",
textLeft: "Batal",
textRight: "Simpan",
onPressRight: () => {
handlerSubmitParticipans();
},
});
}}
>
Simpan
</ButtonCustom>
</ViewWrapper>
);
}

View File

@@ -1,26 +1,54 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { import {
AvatarUsernameAndOtherComponent, BackButton,
BaseBox, DotButton,
DrawerCustom, DrawerCustom,
StackCustom, MenuDrawerDynamicGrid,
TextCustom, ViewWrapper
ViewWrapper,
} from "@/components"; } from "@/components";
import { ICON_SIZE_SMALL } from "@/constants/constans-value";
import Collaboration_BoxDetailSection from "@/screens/Collaboration/BoxDetailSection"; import Collaboration_BoxDetailSection from "@/screens/Collaboration/BoxDetailSection";
import { MaterialIcons } from "@expo/vector-icons"; import { apiCollaborationGetOne } from "@/service/api-client/api-collaboration";
import { useLocalSearchParams } from "expo-router"; import { Ionicons } from "@expo/vector-icons";
import { useState } from "react"; import { router, Stack, useFocusEffect, useLocalSearchParams } from "expo-router";
import { useCallback, useState } from "react";
export default function CollaborationDetailParticipant() { export default function CollaborationDetailParticipant() {
const { id } = useLocalSearchParams(); const { id } = useLocalSearchParams();
const [openDrawerParticipant, setOpenDrawerParticipant] = useState(false); const [openDrawerParticipant, setOpenDrawerParticipant] = useState(false);
const [data, setData] = useState<any>();
useFocusEffect(
useCallback(() => {
onLoadData();
}, [id])
);
const onLoadData = async () => {
try {
const response = await apiCollaborationGetOne({ id: id as string });
if (response.success) {
setData(response.data);
}
} catch (error) {
console.log("[ERROR]", error);
}
};
return ( return (
<> <>
<Stack.Screen
options={{
title: "Detail Proyek",
headerLeft: () => <BackButton />,
headerRight: () => (
<DotButton onPress={() => setOpenDrawerParticipant(true)} />
),
}}
/>
<ViewWrapper> <ViewWrapper>
<Collaboration_BoxDetailSection id={id as string} /> <Collaboration_BoxDetailSection data={data} />
<BaseBox style={{ height: 500 }}> {/* <BaseBox style={{ height: 500 }}>
<TextCustom align="center" bold size="large"> <TextCustom align="center" bold size="large">
Partisipan Partisipan
</TextCustom> </TextCustom>
@@ -39,13 +67,33 @@ export default function CollaborationDetailParticipant() {
} }
/> />
))} ))}
</BaseBox> </BaseBox> */}
</ViewWrapper> </ViewWrapper>
<DrawerCustom <DrawerCustom
isVisible={openDrawerParticipant} isVisible={openDrawerParticipant}
closeDrawer={() => setOpenDrawerParticipant(false)} closeDrawer={() => setOpenDrawerParticipant(false)}
height={"auto"} height={"auto"}
>
<MenuDrawerDynamicGrid
data={[
{
icon: <Ionicons name="people" size={24} color="white" />,
label: "Daftar Partisipan",
path: `/collaboration/${id}/list-of-participants`,
},
]}
onPressItem={(item) => {
router.push(item.path as any);
setOpenDrawerParticipant(false);
}}
/>
</DrawerCustom>
{/* <DrawerCustom
isVisible={openDrawerParticipant}
closeDrawer={() => setOpenDrawerParticipant(false)}
height={"auto"}
> >
<StackCustom> <StackCustom>
<TextCustom bold>Deskripsi Diri</TextCustom> <TextCustom bold>Deskripsi Diri</TextCustom>
@@ -56,7 +104,7 @@ export default function CollaborationDetailParticipant() {
Temporibus iusto soluta necessitatibus. Temporibus iusto soluta necessitatibus.
</TextCustom> </TextCustom>
</StackCustom> </StackCustom>
</DrawerCustom> </DrawerCustom> */}
</> </>
); );
} }

View File

@@ -1,30 +1,65 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { import {
AlertDefaultSystem,
BackButton, BackButton,
ButtonCustom,
DotButton, DotButton,
DrawerCustom, DrawerCustom,
LoaderCustom,
MenuDrawerDynamicGrid, MenuDrawerDynamicGrid,
Spacing, Spacing,
StackCustom, ViewWrapper
TextCustom,
ViewWrapper,
} from "@/components"; } from "@/components";
import { IconEdit } from "@/components/_Icon"; import { IconEdit } from "@/components/_Icon";
import Collaboration_BoxDetailSection from "@/screens/Collaboration/BoxDetailSection"; import Collaboration_BoxDetailSection from "@/screens/Collaboration/BoxDetailSection";
import Collaboration_MainParticipanSelectedSection from "@/screens/Collaboration/ProjectMainSelectedSection"; import {
import { router, Stack, useLocalSearchParams } from "expo-router"; apiCollaborationGetOne
import { useState } from "react"; } from "@/service/api-client/api-collaboration";
import { MaterialIcons } from "@expo/vector-icons";
import {
router,
Stack,
useFocusEffect,
useLocalSearchParams,
} from "expo-router";
import { useCallback, useState } from "react";
export default function CollaborationDetailProjectMain() { export default function CollaborationDetailProjectMain() {
const { id } = useLocalSearchParams(); const { id } = useLocalSearchParams();
const [openDrawer, setOpenDrawer] = useState(false); const [openDrawer, setOpenDrawer] = useState(false);
const [openDrawerParticipant, setOpenDrawerParticipant] = useState(false); const [data, setData] = useState<any>();
const [selected, setSelected] = useState<(string | number)[]>([]); const [loadingGetData, setLoadingGetData] = useState(false);
const handleEdit = () => { useFocusEffect(
console.log("Edit collaboration"); useCallback(() => {
router.push("/(application)/(user)/collaboration/(id)/edit"); handlerLoadData();
}, [id])
);
const handlerLoadData = async () => {
try {
setLoadingGetData(true);
await onLoadData();
} catch (error) {
console.log("[ERROR]", error);
} finally {
setLoadingGetData(false);
}
};
const onLoadData = async () => {
try {
const response = await apiCollaborationGetOne({ id: id as string });
if (response.success) {
setData(response.data);
}
} catch (error) {
console.log("[ERROR]", error);
}
};
const handleSubmit = (item: any) => {
console.log("item :", item);
router.push(item.path);
setOpenDrawer(false);
}; };
return ( return (
@@ -37,34 +72,21 @@ export default function CollaborationDetailProjectMain() {
}} }}
/> />
<ViewWrapper> <ViewWrapper>
<Collaboration_BoxDetailSection id={id as string} /> {loadingGetData ? (
<Collaboration_MainParticipanSelectedSection <LoaderCustom />
selected={selected} ) : (
setSelected={setSelected} <>
setOpenDrawerParticipant={setOpenDrawerParticipant} <Collaboration_BoxDetailSection data={data} />
/> {/* <Collaboration_MainParticipanSelectedSection
selected={selected}
setSelected={setSelected}
setOpenDrawerParticipant={setOpenDrawerParticipant}
listData={listData as any}
/> */}
<ButtonCustom <Spacing />
onPress={() => { </>
AlertDefaultSystem({ )}
title: "Buat Grup",
message:
"Apakah anda yakin ingin membuat grup untuk proyek ini ?",
textLeft: "Tidak",
textRight: "Ya",
onPressLeft: () => {},
onPressRight: () => {
router.navigate(
"/(application)/(user)/collaboration/(tabs)/group"
);
console.log("selected :", selected);
},
});
}}
>
Buat Grup
</ButtonCustom>
<Spacing />
</ViewWrapper> </ViewWrapper>
<DrawerCustom <DrawerCustom
@@ -76,31 +98,20 @@ export default function CollaborationDetailProjectMain() {
data={[ data={[
{ {
label: "Edit", label: "Edit",
path: "/(application)/(user)/collaboration/(tabs)/group", path: `/(application)/(user)/collaboration/${id}/edit`,
icon: <IconEdit />, icon: <IconEdit />,
}, },
{
label: "Pilih Partisipan",
path: `/(application)/(user)/collaboration/${id}/select-of-participants`,
icon: <MaterialIcons name="checklist" size={24} color="white" />,
},
]} ]}
onPressItem={(item) => { onPressItem={(item: any) => {
handleEdit(); handleSubmit(item);
}} }}
/> />
</DrawerCustom> </DrawerCustom>
<DrawerCustom
isVisible={openDrawerParticipant}
closeDrawer={() => setOpenDrawerParticipant(false)}
height={"auto"}
>
<StackCustom>
<TextCustom bold>Deskripsi Diri</TextCustom>
<TextCustom>
Lorem ipsum dolor sit, amet consectetur adipisicing elit. Commodi,
itaque adipisci. Voluptas, sed quod! Ad facere labore voluptates,
neque quidem aut reprehenderit ducimus mollitia quisquam temporibus!
Temporibus iusto soluta necessitatibus.
</TextCustom>
</StackCustom>
</DrawerCustom>
</> </>
); );
} }

View File

@@ -1,53 +1,171 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { import {
ButtonCustom, ButtonCustom,
LoaderCustom,
SelectCustom, SelectCustom,
StackCustom, StackCustom,
TextAreaCustom, TextAreaCustom,
TextInputCustom, TextInputCustom,
ViewWrapper, ViewWrapper,
} from "@/components"; } from "@/components";
import { router } from "expo-router"; import {
apiCollaborationEditData,
apiCollaborationGetOne,
} from "@/service/api-client/api-collaboration";
import { apiMasterCollaborationType } from "@/service/api-client/api-master";
import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
import { useCallback, useState } from "react";
import Toast from "react-native-toast-message";
export default function CollaborationEdit() { export default function CollaborationEdit() {
const { id } = useLocalSearchParams();
console.log("id :", id);
const [data, setData] = useState<any>();
const [listMaster, setListMaster] = useState<any[]>([]);
const [loadingData, setLoadingData] = useState(false);
const [isLoading, setIsLoading] = useState(false);
useFocusEffect(
useCallback(() => {
const fetchData = async () => {
try {
setLoadingData(true);
await onLoadData();
await onLoadMaster();
} catch (error) {
console.log("[ERROR]", error);
} finally {
setLoadingData(false);
}
};
fetchData();
}, [id])
);
const onLoadData = async () => {
try {
const response = await apiCollaborationGetOne({ id: id as string });
if (response.success) {
setData(response.data);
}
} catch (error) {
console.log("[ERROR]", error);
}
};
async function onLoadMaster() {
try {
const response = await apiMasterCollaborationType();
setListMaster(response.data);
} catch (error) {
console.log("[ERROR]", error);
}
}
const handlerSubmitUpdate = async () => {
if (
!data?.title ||
!data?.lokasi ||
!data?.projectCollaborationMaster_IndustriId ||
!data?.purpose ||
!data?.benefit
) {
Toast.show({
type: "error",
text1: "Gagal",
text2: "Harap isi semua data",
});
return;
}
try {
setIsLoading(true);
const response = await apiCollaborationEditData({
id: id as string,
data: data,
});
if (response.success) {
Toast.show({
type: "success",
text1: response.message,
});
router.back();
} else {
Toast.show({
type: "error",
text1: response.message,
});
}
} catch (error) {
console.log("[ERROR]", error);
} finally {
setIsLoading(false);
}
};
return ( return (
<ViewWrapper> <ViewWrapper>
<StackCustom gap={"xs"}> {loadingData ? (
<TextInputCustom label="Judul" placeholder="Masukan judul" required /> <LoaderCustom />
<TextInputCustom label="Lokasi" placeholder="Masukan lokasi" required /> ) : (
<SelectCustom <StackCustom gap={"xs"}>
label="Pilih Industri" <TextInputCustom
data={[ label="Judul"
{ label: "Industri 1", value: "industri-1" }, placeholder="Masukan judul"
{ label: "Industri 2", value: "industri-2" }, required
{ label: "Industri 3", value: "industri-3" }, value={data?.title}
]} onChangeText={(value) => setData({ ...data, title: value })}
onChange={(value) => console.log(value)} />
/> <TextInputCustom
label="Lokasi"
placeholder="Masukan lokasi"
required
value={data?.lokasi}
onChangeText={(value) => setData({ ...data, lokasi: value })}
/>
<SelectCustom
label="Pilih Industri"
data={listMaster?.map((item: any) => ({
label: item.name,
value: item.id,
}))}
value={data?.projectCollaborationMaster_IndustriId}
onChange={(value) =>
setData({ ...data, projectCollaborationMaster_IndustriId: value })
}
/>
<TextAreaCustom <TextAreaCustom
required required
label="Tujuan Proyek" label="Tujuan Proyek"
placeholder="Masukan tujuan proyek" placeholder="Masukan tujuan proyek"
showCount showCount
maxLength={1000} maxLength={1000}
/> value={data?.purpose}
onChangeText={(value) => setData({ ...data, purpose: value })}
/>
<TextAreaCustom <TextAreaCustom
required required
label="Keuntungan Proyek" label="Keuntungan Proyek"
placeholder="Masukan keuntungan proyek" placeholder="Masukan keuntungan proyek"
showCount showCount
maxLength={1000} maxLength={1000}
/> value={data?.benefit}
onChangeText={(value) => setData({ ...data, benefit: value })}
/>
<ButtonCustom <ButtonCustom
title="Update" isLoading={isLoading}
onPress={() => { title="Update"
console.log("Update proyek"); onPress={() => {
router.back(); handlerSubmitUpdate();
}} }}
/> />
</StackCustom> </StackCustom>
)}
</ViewWrapper> </ViewWrapper>
); );
} }

View File

@@ -1,22 +1,75 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { import {
AlertDefaultSystem,
BackButton, BackButton,
ButtonCustom, ButtonCustom,
DotButton, DotButton,
DrawerCustom, DrawerCustom,
InformationBox,
LoaderCustom,
MenuDrawerDynamicGrid, MenuDrawerDynamicGrid,
TextAreaCustom,
ViewWrapper, ViewWrapper,
} from "@/components"; } from "@/components";
import { useAuth } from "@/hooks/use-auth";
import Collaboration_BoxDetailSection from "@/screens/Collaboration/BoxDetailSection"; import Collaboration_BoxDetailSection from "@/screens/Collaboration/BoxDetailSection";
import {
apiCollaborationGetOne,
apiCollaborationGetParticipants,
} from "@/service/api-client/api-collaboration";
import { Ionicons } from "@expo/vector-icons"; import { Ionicons } from "@expo/vector-icons";
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 CollaborationDetail() { export default function CollaborationDetail() {
const { user } = useAuth();
const { id } = useLocalSearchParams(); const { id } = useLocalSearchParams();
const [openDrawerPartisipasi, setOpenDrawerPartisipasi] = useState(false); const [data, setData] = useState<any>();
const [openDrawerMenu, setOpenDrawerMenu] = useState(false); const [openDrawerMenu, setOpenDrawerMenu] = useState(false);
const [isParticipant, setIsParticipant] = useState(false);
const [loadingIsParticipant, setLoadingIsParticipant] = useState(false);
useFocusEffect(
useCallback(() => {
onLoadData();
onLoadParticipants();
}, [id])
);
const onLoadData = async () => {
try {
const response = await apiCollaborationGetOne({ id: id as string });
if (response.success) {
setData(response.data);
}
} catch (error) {
console.log("[ERROR]", error);
}
};
const onLoadParticipants = async () => {
try {
setLoadingIsParticipant(true);
const response = await apiCollaborationGetParticipants({
category: "check-participant",
id: id as string,
authorId: user?.id,
});
if (response.success) {
setIsParticipant(response.data);
}
} catch (error) {
console.log("[ERROR]", error);
} finally {
setLoadingIsParticipant(false);
}
};
return ( return (
<> <>
<Stack.Screen <Stack.Screen
@@ -29,15 +82,35 @@ export default function CollaborationDetail() {
}} }}
/> />
<ViewWrapper> <ViewWrapper>
<Collaboration_BoxDetailSection id={id as string} /> {!data && !isParticipant ? (
<LoaderCustom />
<ButtonCustom onPress={() => setOpenDrawerPartisipasi(true)}> ) : (
Partisipasi <>
</ButtonCustom> {user?.id === data?.Author?.id && (
<InformationBox
text={
"Tombol partisipasi hanya muncul pada proyek milik orang lain"
}
/>
)}
<Collaboration_BoxDetailSection data={data} />
{user?.id !== data?.Author?.id && (
<ButtonCustom
disabled={isParticipant || loadingIsParticipant}
onPress={() => {
router.push(`/collaboration/${id}/create-pacticipants`);
// setOpenDrawerPartisipasi(true);
}}
>
{isParticipant ? "Anda telah berpartisipasi" : "Partisipasi"}
</ButtonCustom>
)}
</>
)}
</ViewWrapper> </ViewWrapper>
{/* Drawer Partisipasi */} {/* Drawer Partisipasi */}
<DrawerCustom {/* <DrawerCustom
isVisible={openDrawerPartisipasi} isVisible={openDrawerPartisipasi}
closeDrawer={() => setOpenDrawerPartisipasi(false)} closeDrawer={() => setOpenDrawerPartisipasi(false)}
height={300} height={300}
@@ -48,6 +121,8 @@ export default function CollaborationDetail() {
required required
showCount showCount
maxLength={500} maxLength={500}
value={description}
onChangeText={setDescription}
/> />
<ButtonCustom <ButtonCustom
@@ -58,19 +133,21 @@ export default function CollaborationDetail() {
message: "Apakah anda sudah yakin ingin menyimpan data ini ?", message: "Apakah anda sudah yakin ingin menyimpan data ini ?",
textLeft: "Batal", textLeft: "Batal",
textRight: "Simpan", textRight: "Simpan",
onPressRight: () => router.replace(`/collaboration/(tabs)/group`), onPressRight: () => {
handlerSubmitParticipans();
},
}); });
}} }}
> >
Simpan Simpan
</ButtonCustom> </ButtonCustom>
</DrawerCustom> </DrawerCustom> */}
{/* Drawer Menu */} {/* Drawer Menu */}
<DrawerCustom <DrawerCustom
isVisible={openDrawerMenu} isVisible={openDrawerMenu}
closeDrawer={() => setOpenDrawerMenu(false)} closeDrawer={() => setOpenDrawerMenu(false)}
height={250} height={"auto"}
> >
<MenuDrawerDynamicGrid <MenuDrawerDynamicGrid
data={[ data={[

View File

@@ -1,38 +1,79 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { import {
AvatarUsernameAndOtherComponent, AvatarUsernameAndOtherComponent,
BaseBox, BaseBox,
DrawerCustom, DrawerCustom,
Spacing, LoaderCustom,
StackCustom, Spacing,
TextCustom, StackCustom,
ViewWrapper TextCustom,
ViewWrapper,
} from "@/components"; } from "@/components";
import { apiCollaborationGetParticipants } from "@/service/api-client/api-collaboration";
import { Feather } from "@expo/vector-icons"; import { Feather } from "@expo/vector-icons";
import { useLocalSearchParams } from "expo-router"; import { useLocalSearchParams } from "expo-router";
import { useState } from "react"; import _ from "lodash";
import { useEffect, useState } from "react";
import { ScrollView } from "react-native"; import { ScrollView } from "react-native";
export default function CollaborationListOfParticipants() { export default function CollaborationListOfParticipants() {
const { id } = useLocalSearchParams(); const { id } = useLocalSearchParams();
const [listData, setListData] = useState<any[]>();
const [loadingGetData, setLoadingGetData] = useState(false);
const [description, setDescription] = useState("");
useEffect(() => {
onLoadData();
}, [id]);
const onLoadData = async () => {
try {
setLoadingGetData(true);
const response = await apiCollaborationGetParticipants({
category: "list",
id: id as string,
});
if (response.success) {
setListData(response.data);
}
} catch (error) {
console.log("[ERROR]", error);
} finally {
setLoadingGetData(false);
}
};
const [openDrawer, setOpenDrawer] = useState(false); const [openDrawer, setOpenDrawer] = useState(false);
return ( return (
<> <>
<ViewWrapper> <ViewWrapper>
{Array.from({ length: 10 }).map((_, index) => ( {loadingGetData ? (
<BaseBox key={index} paddingBlock={5}> <LoaderCustom />
<AvatarUsernameAndOtherComponent ) : _.isEmpty(listData) ? (
avatarHref={`/profile/${id}`} <TextCustom align="center">Tidak ada partisipan</TextCustom>
rightComponent={ ) : (
<Feather listData?.map((item: any, index: number) => (
name="chevron-right" <BaseBox key={index} paddingBlock={5}>
size={24} <AvatarUsernameAndOtherComponent
color="white" avatar={item?.User?.Profile?.imageId}
onPress={() => setOpenDrawer(true)} avatarHref={`/profile/${item?.User?.Profile?.id}`}
/> name={item?.User?.username}
} rightComponent={
/> <Feather
</BaseBox> name="chevron-right"
))} size={24}
color="white"
onPress={() => {
setDescription(item?.deskripsi_diri);
setOpenDrawer(true);
}}
/>
}
/>
</BaseBox>
))
)}
</ViewWrapper> </ViewWrapper>
{/* Drawer */} {/* Drawer */}
@@ -44,34 +85,7 @@ export default function CollaborationListOfParticipants() {
<TextCustom bold>Deskripsi diri</TextCustom> <TextCustom bold>Deskripsi diri</TextCustom>
<BaseBox> <BaseBox>
<ScrollView style={{ height: "80%" }}> <ScrollView style={{ height: "80%" }}>
<TextCustom> <TextCustom>{description}</TextCustom>
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
eiusmod tempor incididunt ut labore et dolore magna aliqua.
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
eiusmod tempor incididunt ut labore et dolore magna aliqua.Lorem
ipsum dolor sit amet, consectetur adipiscing elit, sed do
eiusmod tempor incididunt ut labore et dolore magna aliqua.Lorem
ipsum dolor sit amet, consectetur adipiscing elit, sed do
eiusmod tempor incididunt ut labore et dolore magna aliqua.Lorem
ipsum dolor sit amet, consectetur adipiscing elit, sed do
eiusmod tempor incididunt ut iqua.Lorem ipsum dolor sit amet,
consectetur adipiscing elit, sed do eiusmod tempor incididunt ut
labore et dolore magna aliqua.Lorem ipsum dolor sit amet,
consectetur adipiscing elit, sed do eiusmod tempor incididunt ut
labore et dolore magna aliqua.Lorem ipsum dolor sit amet,
consectetur adipiscing elit, sed do eiusmod tempor incididunt ut
labore et dolore magna aliqua.Lorem ipsum dolor sit amet,
consectetur adipiscing elit, sed do eiusmod tempor incididunt ut
labore et dolore magna aliqua.Lorem ipsum dolor sit amet,
consectetur adipiscing elit, sed do eiusmod tempor incididunt ut
labore et dolore magna aliqua.Lorem ipsum dolor sit amet,
consectetur adipiscing elit, sed do eiusmod tempor incididunt ut
labore et dolore magna aliqua.Lorem ipsum dolor sit amet,
consectetur adipiscing elit, sed do eiusmod tempor incididunt ut
labore et dolore magna aliqua.Lorem ipsum dolor sit amet,
consectetur adipiscing elit, sed do eiusmod tempor incididunt ut
labore et dolore magna aliqua.
</TextCustom>
</ScrollView> </ScrollView>
</BaseBox> </BaseBox>
<Spacing /> <Spacing />

View File

@@ -0,0 +1,251 @@
/* eslint-disable react-hooks/exhaustive-deps */
import {
AlertDefaultSystem,
AvatarComp,
BaseBox,
BoxButtonOnFooter,
ButtonCustom,
CheckboxCustom,
CheckboxGroup,
DrawerCustom,
Grid,
LoaderCustom,
Spacing,
StackCustom,
TextAreaCustom,
TextCustom,
ViewWrapper,
} from "@/components";
import ModalCustom from "@/components/Modal/ModalCustom";
import { ICON_SIZE_SMALL } from "@/constants/constans-value";
import { useAuth } from "@/hooks/use-auth";
import {
apiCollaborationCreateGroup,
apiCollaborationGetParticipants,
} from "@/service/api-client/api-collaboration";
import { MaterialIcons } from "@expo/vector-icons";
import { router, useLocalSearchParams } from "expo-router";
import _ from "lodash";
import { useEffect, useState } from "react";
import { ScrollView, View } from "react-native";
import Toast from "react-native-toast-message";
export default function CollaborationSelectOfParticipants() {
const { user } = useAuth();
const { id } = useLocalSearchParams();
const [listData, setListData] = useState<any[]>();
const [loadingGetData, setLoadingGetData] = useState(false);
const [description, setDescription] = useState("");
const [selected, setSelected] = useState<(string | number)[]>([]);
const [openDrawer, setOpenDrawer] = useState(false);
const [nameGroup, setNameGroup] = useState("");
const [openModal, setOpenModal] = useState(false);
const [isLoading, setIsLoading] = useState(false);
useEffect(() => {
onLoadData();
}, [id]);
const onLoadData = async () => {
try {
setLoadingGetData(true);
const response = await apiCollaborationGetParticipants({
category: "list",
id: id as string,
});
// console.log("response :", JSON.stringify(response.data, null, 2));
if (response.success) {
setListData(response.data);
}
} catch (error) {
console.log("[ERROR]", error);
} finally {
setLoadingGetData(false);
}
};
const handlerCreateGroup = async () => {
if (_.isEmpty(nameGroup)) {
Toast.show({
type: "error",
text1: "Nama grup tidak boleh kosong",
});
return;
}
try {
setIsLoading(true);
const response = await apiCollaborationCreateGroup({
id: id as string,
data: {
authorId: user?.id,
nameGroup: nameGroup,
listSelect: selected,
},
});
if (response.success) {
Toast.show({
type: "success",
text1: "Grup berhasil dibuat",
});
router.push("/(application)/(user)/collaboration/(tabs)/group");
} else {
Toast.show({
type: "error",
text1: "Gagal membuat grup",
});
}
} catch (error) {
console.log("[ERROR]", error);
} finally {
setIsLoading(false);
}
};
const handlerSubmit = () => {
return (
<>
<BoxButtonOnFooter>
<ButtonCustom
disabled={_.isEmpty(selected)}
onPress={() => {
setOpenModal(true);
setNameGroup("");
}}
>
Buat Grup
</ButtonCustom>
</BoxButtonOnFooter>
</>
);
};
return (
<>
<ViewWrapper
footerComponent={_.isEmpty(listData) ? null : handlerSubmit()}
>
{loadingGetData ? (
<LoaderCustom />
) : _.isEmpty(listData) ? (
<TextCustom align="center">Tidak ada partisipan</TextCustom>
) : (
<StackCustom>
<TextCustom size="default" color="red" bold>
*{" "}
<TextCustom size="small" semiBold>
Pilih user yang akan menjadi tim proyek anda
</TextCustom>
</TextCustom>
<CheckboxGroup
value={selected}
onChange={(val: any) => {
console.log("val :", val);
setSelected(val);
}}
>
{listData?.map((item: any, index: any) => (
<View key={index}>
<Grid key={index}>
<Grid.Col
span={2}
style={{ alignItems: "center", justifyContent: "center" }}
>
<CheckboxCustom valueKey={item?.User?.id} />
</Grid.Col>
<Grid.Col span={2} style={{ alignItems: "center" }}>
<AvatarComp
size="base"
fileId={item?.User?.Profile?.imageId}
/>
</Grid.Col>
<Grid.Col span={6} style={{ justifyContent: "center" }}>
<TextCustom bold truncate>
{item?.User?.username}
</TextCustom>
</Grid.Col>
<Grid.Col
span={2}
style={{ alignItems: "center", justifyContent: "center" }}
>
<MaterialIcons
name="notes"
size={ICON_SIZE_SMALL}
color="white"
onPress={() => {
setOpenDrawer(true);
setDescription(item?.deskripsi_diri);
}}
/>
</Grid.Col>
</Grid>
</View>
))}
</CheckboxGroup>
</StackCustom>
)}
</ViewWrapper>
<ModalCustom isVisible={openModal}>
<StackCustom gap={0}>
<TextAreaCustom
placeholder="Nama Grup"
value={nameGroup}
onChangeText={(val) => setNameGroup(val)}
/>
<Grid>
<Grid.Col span={6} style={{ paddingRight: 10 }}>
<ButtonCustom
backgroundColor="gray"
onPress={() => {
setOpenModal(false);
}}
>
Batal
</ButtonCustom>
</Grid.Col>
<Grid.Col span={6} style={{ paddingLeft: 10 }}>
<ButtonCustom
isLoading={isLoading}
disabled={_.isEmpty(nameGroup)}
onPress={() => {
AlertDefaultSystem({
title: "Buat Grup",
message:
"Apakah anda yakin ingin membuat grup untuk proyek ini ?",
textLeft: "Tidak",
textRight: "Ya",
onPressLeft: () => {},
onPressRight: () => {
handlerCreateGroup();
},
});
}}
>
Simpan
</ButtonCustom>
</Grid.Col>
</Grid>
</StackCustom>
</ModalCustom>
{/* Drawer */}
<DrawerCustom
isVisible={openDrawer}
closeDrawer={() => setOpenDrawer(false)}
>
<StackCustom>
<TextCustom bold>Deskripsi diri</TextCustom>
<BaseBox>
<ScrollView style={{ height: "80%" }}>
<TextCustom>{description}</TextCustom>
</ScrollView>
</BaseBox>
<Spacing />
</StackCustom>
</DrawerCustom>
</>
);
}

View File

@@ -1,53 +1,174 @@
import { import {
ButtonCustom, ButtonCustom,
SelectCustom, LoaderCustom,
StackCustom, SelectCustom,
TextAreaCustom, StackCustom,
TextInputCustom, TextAreaCustom,
ViewWrapper TextInputCustom,
ViewWrapper,
} from "@/components"; } from "@/components";
import { useAuth } from "@/hooks/use-auth";
import { apiCollaborationCreate } from "@/service/api-client/api-collaboration";
import { apiMasterCollaborationType } from "@/service/api-client/api-master";
import { router } from "expo-router"; import { router } from "expo-router";
import React, { useEffect, useState } from "react";
import Toast from "react-native-toast-message";
interface CollaborationCreateProps {
title?: string;
lokasi?: string;
purpose?: string;
benefit?: string;
projectCollaborationMaster_IndustriId?: string;
userId?: string;
}
export default function CollaborationCreate() { export default function CollaborationCreate() {
const { user } = useAuth();
const [listMaster, setListMaster] = useState<any>([]);
const [loadingMaster, setLoadingMaster] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const [data, setData] = React.useState<CollaborationCreateProps>({
title: "",
lokasi: "",
purpose: "",
benefit: "",
projectCollaborationMaster_IndustriId: "",
userId: "",
});
useEffect(() => {
onLoadMaster();
}, []);
async function onLoadMaster() {
try {
setLoadingMaster(true);
const response = await apiMasterCollaborationType();
setListMaster(response.data);
} catch (error) {
console.log("[ERROR]", error);
} finally {
setLoadingMaster(false);
}
}
const handlerSubmit = async () => {
if (
!data?.title ||
!data?.lokasi ||
!data?.purpose ||
!data?.benefit ||
!data?.projectCollaborationMaster_IndustriId
) {
Toast.show({
type: "error",
text1: "Gagal",
text2: "Harap isi semua data",
});
return;
}
const newData: CollaborationCreateProps = {
title: data?.title,
lokasi: data?.lokasi,
purpose: data?.purpose,
benefit: data?.benefit,
projectCollaborationMaster_IndustriId:
data?.projectCollaborationMaster_IndustriId,
userId: user?.id,
};
try {
setIsLoading(true);
const response = await apiCollaborationCreate({ data: newData });
if (response.success) {
Toast.show({
type: "success",
text1: "Berhasil",
text2: response.message,
});
router.back();
} else {
Toast.show({
type: "error",
text1: "Gagal",
text2: response.message,
});
}
} catch (error) {
console.log("[ERROR]", error);
} finally {
setIsLoading(false);
}
};
return ( return (
<ViewWrapper> <ViewWrapper>
<StackCustom gap={"xs"}> {loadingMaster ? (
<TextInputCustom label="Judul" placeholder="Masukan judul" required /> <LoaderCustom />
<TextInputCustom label="Lokasi" placeholder="Masukan lokasi" required /> ) : (
<SelectCustom <StackCustom gap={"xs"}>
label="Pilih Industri" <TextInputCustom
data={[ label="Judul"
{ label: "Industri 1", value: "industri-1" }, placeholder="Masukan judul"
{ label: "Industri 2", value: "industri-2" }, required
{ label: "Industri 3", value: "industri-3" }, value={data?.title}
]} onChangeText={(value: any) => setData({ ...data, title: value })}
onChange={(value) => console.log(value)} />
/>
<TextAreaCustom <TextInputCustom
required label="Lokasi"
label="Tujuan Proyek" placeholder="Masukan lokasi"
placeholder="Masukan tujuan proyek" required
showCount value={data?.lokasi}
maxLength={1000} onChangeText={(value: any) => setData({ ...data, lokasi: value })}
/> />
<TextAreaCustom <SelectCustom
required label="Pilih Industri"
label="Keuntungan Proyek" data={listMaster?.map((item: any) => ({
placeholder="Masukan keuntungan proyek" label: item.name,
showCount value: item.id,
maxLength={1000} }))}
/> value={data?.projectCollaborationMaster_IndustriId}
onChange={(value: any) => {
console.log(value);
setData({
...data,
projectCollaborationMaster_IndustriId: value,
});
}}
/>
<ButtonCustom <TextAreaCustom
title="Simpan" required
onPress={() => { label="Tujuan Proyek"
console.log("Simpan proyek"); placeholder="Masukan tujuan proyek"
router.back(); showCount
}} maxLength={1000}
/> value={data?.purpose}
</StackCustom> onChangeText={(value: any) => setData({ ...data, purpose: value })}
/>
<TextAreaCustom
required
label="Keuntungan Proyek"
placeholder="Masukan keuntungan proyek"
showCount
maxLength={1000}
value={data?.benefit}
onChangeText={(value: any) => setData({ ...data, benefit: value })}
/>
<ButtonCustom
isLoading={isLoading}
title="Simpan"
onPress={() => handlerSubmit()}
/>
</StackCustom>
)}
</ViewWrapper> </ViewWrapper>
); );
} }

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,27 +1,57 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { import {
BoxWithHeaderSection, BoxWithHeaderSection,
Grid, Grid,
ScrollableCustom, LoaderCustom,
StackCustom, ScrollableCustom,
TextCustom StackCustom,
TextCustom,
} from "@/components"; } from "@/components";
import ViewWrapper from "@/components/_ShareComponent/ViewWrapper"; import ViewWrapper from "@/components/_ShareComponent/ViewWrapper";
import { useAuth } from "@/hooks/use-auth";
import { dummyMasterStatus } from "@/lib/dummy-data/_master/status"; import { dummyMasterStatus } from "@/lib/dummy-data/_master/status";
import { useState } from "react"; import { apiEventGetByStatus } from "@/service/api-client/api-event";
import { useFocusEffect } from "expo-router";
import _ from "lodash";
import { useCallback, useState } from "react";
export default function EventStatus() { export default function EventStatus() {
const id = "test-id-event"; 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 apiEventGetByStatus({
id: id!,
status: activeCategory!,
});
// console.log("Response", JSON.stringify(response.data, null, 2));
setListData(response.data);
} catch (error) {
console.log(error);
} finally {
setLoadingGetData(false);
}
}
const handlePress = (item: any) => { const handlePress = (item: any) => {
setActiveCategory(item.value); setActiveCategory(item.value);
// tambahkan logika lain seperti filter dsb. // tambahkan logika lain seperti filter dsb.
}; };
const scrollComponent = ( const tabsComponent = (
<ScrollableCustom <ScrollableCustom
data={dummyMasterStatus.map((e, i) => ({ data={dummyMasterStatus.map((e, i) => ({
id: i, id: i,
@@ -34,30 +64,36 @@ export default function EventStatus() {
); );
return ( return (
<ViewWrapper headerComponent={scrollComponent}> <ViewWrapper headerComponent={tabsComponent}>
<BoxWithHeaderSection href={`/event/${id}/${activeCategory}/detail-event`}> {loadingGetData ? (
<StackCustom gap={"xs"}> <LoaderCustom />
<Grid> ) : _.isEmpty(listData) ? (
<Grid.Col span={8}> <TextCustom align="center">Tidak ada data {activeCategory}</TextCustom>
<TextCustom truncate bold> ) : (
Lorem ipsum,{" "} listData.map((item: any, i) => (
<TextCustom color="green">{activeCategory}</TextCustom> dolor <BoxWithHeaderSection
sit amet consectetur adipisicing elit. key={i}
</TextCustom> href={`/event/${item.id }/${activeCategory}/detail-event`}
</Grid.Col> >
<Grid.Col span={4} style={{ alignItems: "flex-end" }}> <StackCustom gap={"xs"}>
<TextCustom>{new Date().toLocaleDateString()}</TextCustom> <Grid>
</Grid.Col> <Grid.Col span={8}>
</Grid> <TextCustom truncate bold>
{item?.title}
</TextCustom>
</Grid.Col>
<Grid.Col span={4} style={{ alignItems: "flex-end" }}>
<TextCustom>
{new Date(item?.tanggal).toLocaleDateString()}
</TextCustom>
</Grid.Col>
</Grid>
<TextCustom truncate={2}> <TextCustom truncate={2}>{item?.deskripsi}</TextCustom>
Lorem ipsum dolor sit amet consectetur adipisicing elit. Consectetur </StackCustom>
eveniet ab eum ducimus tempore a quia deserunt quisquam. Tempora, </BoxWithHeaderSection>
atque. Aperiam minima asperiores dicta perferendis quis adipisci, ))
dolore optio porro! )}
</TextCustom>
</StackCustom>
</BoxWithHeaderSection>
</ViewWrapper> </ViewWrapper>
); );
} }

View File

@@ -1,3 +1,4 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { import {
BaseBox, BaseBox,
DotButton, DotButton,
@@ -11,17 +12,64 @@ import {
} 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_AlertButtonStatusSection from "@/screens/Event/AlertButtonStatusSection";
import Event_ButtonStatusSection from "@/screens/Event/ButtonStatusSection"; import Event_ButtonStatusSection from "@/screens/Event/ButtonStatusSection";
import { menuDrawerDraftEvent } from "@/screens/Event/menuDrawerDraft"; import { menuDrawerDraftEvent } from "@/screens/Event/menuDrawerDraft";
import { router, Stack, useLocalSearchParams } from "expo-router"; import { apiEventGetOne } from "@/service/api-client/api-event";
import { useState } from "react"; import { dateTimeView } from "@/utils/dateTimeView";
import {
router,
Stack,
useFocusEffect,
useLocalSearchParams,
} from "expo-router";
import { useCallback, useState } from "react";
export default function EventDetailStatus() { export default function EventDetailStatus() {
const { id, status } = useLocalSearchParams(); const { id, status } = useLocalSearchParams();
const [openDrawer, setOpenDrawer] = useState(false); const [openDrawer, setOpenDrawer] = useState(false);
const [openAlert, setOpenAlert] = useState(false); // const [openAlert, setOpenAlert] = useState(false);
const [openDeleteAlert, setOpenDeleteAlert] = useState(false);
const [data, setData] = useState<any>();
useFocusEffect(
useCallback(() => {
onLoadData();
}, [id])
);
async function onLoadData() {
try {
const response = await apiEventGetOne({ id: id as string });
if (response.success) {
setData(response.data);
}
} catch (error) {
console.log("[ERROR]", error);
}
}
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 || "-",
},
];
const handlePress = (item: IMenuDrawerItem) => { const handlePress = (item: IMenuDrawerItem) => {
console.log("PATH >> ", item.path); console.log("PATH >> ", item.path);
@@ -45,7 +93,7 @@ export default function EventDetailStatus() {
<BaseBox> <BaseBox>
<StackCustom> <StackCustom>
<TextCustom bold align="center" size="xlarge"> <TextCustom bold align="center" size="xlarge">
Judul event {status} {data?.title || "-"}
</TextCustom> </TextCustom>
{listData.map((item, index) => ( {listData.map((item, index) => (
<Grid key={index}> <Grid key={index}>
@@ -60,9 +108,8 @@ export default function EventDetailStatus() {
</StackCustom> </StackCustom>
</BaseBox> </BaseBox>
<Event_ButtonStatusSection <Event_ButtonStatusSection
id={id as string}
status={status as string} status={status as string}
onOpenAlert={setOpenAlert}
onOpenDeleteAlert={setOpenDeleteAlert}
/> />
<Spacing /> <Spacing />
</ViewWrapper> </ViewWrapper>
@@ -70,7 +117,7 @@ export default function EventDetailStatus() {
<DrawerCustom <DrawerCustom
isVisible={openDrawer} isVisible={openDrawer}
closeDrawer={() => setOpenDrawer(false)} closeDrawer={() => setOpenDrawer(false)}
height={250} height={"auto"}
> >
<MenuDrawerDynamicGrid <MenuDrawerDynamicGrid
data={menuDrawerDraftEvent({ id: id as string }) as any} data={menuDrawerDraftEvent({ id: id as string }) as any}
@@ -78,40 +125,6 @@ export default function EventDetailStatus() {
onPressItem={handlePress as any} onPressItem={handlePress as any}
/> />
</DrawerCustom> </DrawerCustom>
<Event_AlertButtonStatusSection
id={id as string}
status={status as string}
openAlert={openAlert}
setOpenAlert={setOpenAlert}
openDeleteAlert={openDeleteAlert}
setOpenDeleteAlert={setOpenDeleteAlert}
/>
</> </>
); );
} }
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

@@ -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

@@ -1,106 +1,271 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { import {
ButtonCustom, ButtonCustom,
LoaderCustom,
SelectCustom, SelectCustom,
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 { masterTypeEvent } from "@/lib/dummy-data/event/master-type-event"; import {
apiEventGetOne,
apiEventUpdateData,
} from "@/service/api-client/api-event";
import { apiMasterEventType } from "@/service/api-client/api-master";
import { DateTimePickerEvent } from "@react-native-community/datetimepicker"; import { DateTimePickerEvent } from "@react-native-community/datetimepicker";
import { router } from "expo-router"; import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
import React, { useState } from "react"; import React, { useCallback, useEffect, useState } from "react";
import { Platform } from "react-native"; import Toast from "react-native-toast-message";
export default function EventEdit() { export default function EventEdit() {
const { id } = useLocalSearchParams();
const [data, setData] = useState<any>();
// {
// title: "",
// lokasi: "",
// deskripsi: "",
// eventMaster_TipeAcaraId: "",
// tanggal: "",
// tanggalSelesai: "",
// authorId: "",
// }
const [listTypeEvent, setListTypeEvent] = useState([]);
const [selectedDate, setSelectedDate] = useState< const [selectedDate, setSelectedDate] = useState<
Date | DateTimePickerEvent | null Date | DateTimePickerEvent | null
>(null); >();
const [selectedEndDate, setSelectedEndDate] = useState< const [selectedEndDate, setSelectedEndDate] = useState<
Date | DateTimePickerEvent | null Date | DateTimePickerEvent | null
>(null); >();
const handlerSubmit = () => { const [isLoading, setIsLoading] = useState(false);
const [isLoadData, setIsLoadData] = useState(false);
useFocusEffect(
useCallback(() => {
onLoadData();
}, [id])
);
async function onLoadData() {
try { try {
if (selectedDate) { setIsLoadData(true);
console.log("Tanggal yang dipilih:", selectedDate); const response = await apiEventGetOne({ id: id as string });
console.log(`ISO Format ${Platform.OS}:`, selectedDate.toString()); if (response.success) {
// Kirim ke API atau proses lanjutan setData(response.data);
} else { setSelectedDate(new Date(response.data.tanggal));
console.log("Tanggal belum dipilih"); setSelectedEndDate(new Date(response.data.tanggalSelesai));
} }
if (selectedEndDate) {
console.log("Tanggal yang dipilih:", selectedEndDate);
console.log(`ISO Format ${Platform.OS}:`, selectedEndDate.toString());
// Kirim ke API atau proses lanjutan
} else {
console.log("Tanggal berakhir belum dipilih");
}
console.log("Data berhasil terupdate");
router.back()
} catch (error) { } catch (error) {
console.log(error); console.log("[ERROR]", error);
} finally {
setIsLoadData(false);
}
}
useEffect(() => {
onLoadMasterEventType();
}, []);
const onLoadMasterEventType = async () => {
try {
const response = await apiMasterEventType();
setListTypeEvent(response.data);
} catch (error) {
console.log("Error onLoadMasterEventType", error);
} }
}; };
const buttonSubmit = ( const validateDate = async () => {
<ButtonCustom title="Update" onPress={handlerSubmit} /> if (
); data?.title === "" ||
data?.lokasi === "" ||
data?.deskripsi === "" ||
data?.eventMaster_TipeAcaraId === ""
) {
Toast.show({
type: "info",
text1: "Info",
text2: "Lengkapi semua data",
});
return false;
}
const startDate = new Date(selectedDate as any);
const endDate = new Date(selectedEndDate as any);
if (startDate >= endDate) {
Toast.show({
type: "info",
text1: "Info",
text2: "Ubah tanggal berakhirnya event",
});
return false;
}
return true;
};
const handlerSubmit = async () => {
const isValid = await validateDate();
if (!isValid) return;
try {
setIsLoading(true);
const newData = {
...data,
tanggal: new Date(selectedDate as any).toISOString(),
tanggalSelesai: new Date(selectedEndDate as any).toISOString(),
};
const response = await apiEventUpdateData({
id: id as string,
data: newData,
});
if (response.success) {
Toast.show({
type: "success",
text1: response.message,
});
return router.back();
}
} catch (error) {
console.log(error);
} finally {
setIsLoading(false);
}
};
const validateDateRange = (
selectedDate: string | Date,
selectedEndDate: string | Date
): { isValid: boolean; error?: string } => {
const startDate = new Date(selectedDate);
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,
};
};
return ( return (
<> <>
<ViewWrapper> <ViewWrapper>
<StackCustom gap={"xs"}> {isLoadData ? (
<TextInputCustom <LoaderCustom />
placeholder="Masukkan nama event" ) : (
label="Nama Event" <StackCustom gap={"xs"}>
required <TextInputCustom
/> placeholder="Masukkan nama event"
<SelectCustom label="Nama Event"
label="Tipe Event" required
placeholder="Pilih tipe event" value={data?.title}
data={masterTypeEvent} onChangeText={(value) => setData({ ...data, title: value })}
onChange={(value) => console.log(value)} />
/> <SelectCustom
<TextInputCustom label="Tipe Event"
label="Lokasi" placeholder="Pilih tipe event"
placeholder="Masukkan lokasi event" data={listTypeEvent.map((item: any) => ({
required label: item.name,
/> value: item.id,
}))}
value={data?.eventMaster_TipeAcaraId || ""}
onChange={(value) => {
console.log(value);
setData({ ...data, eventMaster_TipeAcaraId: value });
}}
/>
<TextInputCustom
label="Lokasi"
placeholder="Masukkan lokasi event"
required
value={data?.lokasi}
onChangeText={(value) => setData({ ...data, lokasi: value })}
/>
<DateTimePickerCustom
minimumDate={new Date(Date.now())}
label="Tanggal & Waktu Mulai"
required
value={selectedDate as any}
onChange={(date: any) => {
setSelectedDate(date as any);
}}
/>
<StackCustom gap={0}>
<DateTimePickerCustom
minimumDate={selectedDate as any}
label="Tanggal & Waktu Berakhir"
required
value={selectedEndDate as any}
onChange={(date: any) => {
setSelectedEndDate(date as any);
}}
/>
<DateTimePickerCustom {/* Muncul */}
label="Tanggal & Waktu Mulai" {validateDateRange(selectedDate as any, selectedEndDate as any)
required .isValid ? (
onChange={(date: Date) => { <TextCustom style={{ color: "green" }}>
setSelectedDate(date as any); {
}} validateDateRange(
value={selectedDate as any} selectedDate as any,
minimumDate={new Date(Date.now())} selectedEndDate as any
/> ).error
}
</TextCustom>
) : (
<TextCustom style={{ color: "red" }}>
{
validateDateRange(
selectedDate as any,
selectedEndDate as any
).error
}
</TextCustom>
)}
<Spacing />
</StackCustom>
<DateTimePickerCustom <TextAreaCustom
label="Tanggal & Waktu Berakhir" label="Deskripsi"
required placeholder="Masukkan deskripsi event"
onChange={(date: Date) => { required
setSelectedEndDate(date as any); showCount
}} maxLength={100}
value={selectedEndDate as any} value={data?.deskripsi}
/> onChangeText={(value) => setData({ ...data, deskripsi: value })}
/>
<TextAreaCustom <ButtonCustom
label="Deskripsi" isLoading={isLoading}
placeholder="Masukkan deskripsi event" title="Update"
required onPress={handlerSubmit}
showCount />
maxLength={100} </StackCustom>
/> )}
{buttonSubmit}
</StackCustom>
</ViewWrapper> </ViewWrapper>
</> </>
); );

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,6 +1,7 @@
import { import {
ButtonCustom, ButtonCustom,
SelectCustom, SelectCustom,
Spacing,
StackCustom, StackCustom,
TextAreaCustom, TextAreaCustom,
TextCustom, TextCustom,
@@ -9,11 +10,12 @@ import {
} from "@/components"; } from "@/components";
import DateTimePickerCustom from "@/components/DateInput/DateTimePickerCustom"; import DateTimePickerCustom from "@/components/DateInput/DateTimePickerCustom";
import { useAuth } from "@/hooks/use-auth"; import { useAuth } from "@/hooks/use-auth";
import { masterTypeEvent } from "@/lib/dummy-data/event/master-type-event"; import { apiEventCreate } from "@/service/api-client/api-event";
import { apiMasterEventType } from "@/service/api-client/api-master";
import { DateTimePickerEvent } from "@react-native-community/datetimepicker"; import { DateTimePickerEvent } from "@react-native-community/datetimepicker";
import { router } from "expo-router"; import { router } from "expo-router";
import React, { useState } from "react"; import React, { useEffect, useState } from "react";
import { Platform } from "react-native"; import Toast from "react-native-toast-message";
interface EventCreateProps { interface EventCreateProps {
title?: string; title?: string;
@@ -27,8 +29,23 @@ interface EventCreateProps {
export default function EventCreate() { export default function EventCreate() {
const [data, setData] = useState<EventCreateProps>(); const [data, setData] = useState<EventCreateProps>();
const [listTypeEvent, setListTypeEvent] = useState([]);
const [isLoading, setIsLoading] = useState(false);
const { user } = useAuth(); const { user } = useAuth();
useEffect(() => {
onLoadMasterEventType();
}, []);
const onLoadMasterEventType = async () => {
try {
const response = await apiMasterEventType();
setListTypeEvent(response.data);
} catch (error) {
console.log("Error onLoadMasterEventType", error);
}
};
const [selectedDate, setSelectedDate] = useState< const [selectedDate, setSelectedDate] = useState<
Date | DateTimePickerEvent | null Date | DateTimePickerEvent | null
>(null); >(null);
@@ -37,35 +54,76 @@ export default function EventCreate() {
Date | DateTimePickerEvent | null Date | DateTimePickerEvent | null
>(null); >(null);
const handlerSubmit = () => { const handlerSubmit = async () => {
if (
!data?.title ||
!data?.lokasi ||
!data?.deskripsi ||
!data?.eventMaster_TipeAcaraId
) {
Toast.show({
type: "info",
text1: "Info",
text2: "Lengkapi semua data",
});
return;
}
if (!selectedDate || !selectedEndDate) {
Toast.show({
type: "info",
text1: "Info",
text2: "Pilih tanggal mulai dan berakhir",
});
return;
}
// if (selectedDate) {
// console.log("Tanggal yang dipilih:", selectedDate);
// console.log(`ISO Format ${Platform.OS}:`, selectedDate.toString());
// // Kirim ke API atau proses lanjutan
// } else {
// console.log("Tanggal belum dipilih");
// }
// if (selectedEndDate) {
// console.log("Tanggal yang dipilih:", selectedEndDate);
// console.log(`ISO Format ${Platform.OS}:`, selectedEndDate.toString());
// // Kirim ke API atau proses lanjutan
// } else {
// console.log("Tanggal berakhir belum dipilih");
// }
try { try {
// if (selectedDate) { setIsLoading(true);
// console.log("Tanggal yang dipilih:", selectedDate);
// console.log(`ISO Format ${Platform.OS}:`, selectedDate.toString());
// // Kirim ke API atau proses lanjutan
// } else {
// console.log("Tanggal belum dipilih");
// }
// if (selectedEndDate) { const newData = {
// console.log("Tanggal yang dipilih:", selectedEndDate); ...data,
// console.log(`ISO Format ${Platform.OS}:`, selectedEndDate.toString()); tanggal: new Date(selectedDate as any).toISOString(),
// // Kirim ke API atau proses lanjutan tanggalSelesai: new Date(selectedEndDate as any).toISOString(),
// } else { authorId: user?.id,
// console.log("Tanggal berakhir belum dipilih"); };
// }
console.log("Data berhasil disimpan", JSON.stringify(data, null, 2)); console.log("Data berhasil disimpan", JSON.stringify(newData, null, 2));
// router.navigate("/event/status");
const response = await apiEventCreate(newData);
console.log("Response", JSON.stringify(response, null, 2));
router.navigate("/event/status");
} catch (error) { } catch (error) {
console.log(error); console.log(error);
} finally {
setIsLoading(false);
} }
}; };
const buttonSubmit = ( const buttonSubmit = (
<ButtonCustom title="Simpan" onPress={handlerSubmit} /> <ButtonCustom
// <BoxButtonOnFooter> isLoading={isLoading}
// </BoxButtonOnFooter> title="Simpan"
onPress={handlerSubmit}
/>
); );
return ( return (
@@ -78,14 +136,20 @@ export default function EventCreate() {
required required
onChangeText={(value: any) => setData({ ...data, title: value })} onChangeText={(value: any) => setData({ ...data, title: value })}
/> />
<SelectCustom <SelectCustom
label="Tipe Event" label="Tipe Event"
placeholder="Pilih tipe event" placeholder="Pilih tipe event"
data={masterTypeEvent} data={listTypeEvent.map((item: any) => ({
label: item.name,
value: item.id,
}))}
value={data?.eventMaster_TipeAcaraId || ""}
onChange={(value: any) => onChange={(value: any) =>
setData({ ...data, eventMaster_TipeAcaraId: value }) setData({ ...data, eventMaster_TipeAcaraId: value })
} }
/> />
<TextInputCustom <TextInputCustom
label="Lokasi" label="Lokasi"
placeholder="Masukkan lokasi event" placeholder="Masukkan lokasi event"
@@ -103,14 +167,24 @@ export default function EventCreate() {
minimumDate={new Date(Date.now())} minimumDate={new Date(Date.now())}
/> />
<DateTimePickerCustom <StackCustom gap={0}>
label="Tanggal & Waktu Berakhir" <DateTimePickerCustom
required disabled={!selectedDate}
onChange={(date: Date) => { label="Tanggal & Waktu Berakhir"
setSelectedEndDate(date as any); required
}} onChange={(date: Date) => {
value={selectedEndDate as any} setSelectedEndDate(date as any);
/> }}
value={selectedEndDate as any}
minimumDate={new Date(selectedDate as any)}
/>
{!selectedDate && (
<TextCustom color="gray" size={"small"}>
Note: Pilih tanggal mulai terlebih dahulu
</TextCustom>
)}
<Spacing />
</StackCustom>
<TextAreaCustom <TextAreaCustom
label="Deskripsi" label="Deskripsi"
@@ -118,6 +192,9 @@ export default function EventCreate() {
required required
showCount showCount
maxLength={100} maxLength={100}
onChangeText={(value: any) =>
setData({ ...data, deskripsi: value })
}
/> />
{buttonSubmit} {buttonSubmit}

View File

@@ -1,37 +1,98 @@
import { import {
BoxButtonOnFooter, BoxButtonOnFooter,
ButtonCustom, ButtonCustom,
LoaderCustom,
TextAreaCustom, TextAreaCustom,
ViewWrapper, ViewWrapper,
} from "@/components"; } from "@/components";
import { router } from "expo-router"; import { apiForumGetOne, apiForumUpdate } from "@/service/api-client/api-forum";
import { useState } from "react"; import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
import { useCallback, useState } from "react";
import Toast from "react-native-toast-message";
export default function ForumEdit() { export default function ForumEdit() {
const { id } = useLocalSearchParams();
const [text, setText] = useState(""); const [text, setText] = useState("");
const [loadingGetData, setLoadingGetData] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const buttonFooter = ( useFocusEffect(
<BoxButtonOnFooter> useCallback(() => {
<ButtonCustom onLoadData(id as string);
onPress={() => { }, [id])
console.log("Posting", text);
router.back();
}}
>
Update
</ButtonCustom>
</BoxButtonOnFooter>
); );
const onLoadData = async (id: string) => {
try {
setLoadingGetData(true);
const response = await apiForumGetOne({ id });
setText(response.data.diskusi);
} catch (error) {
console.log("[ERROR]", error);
} finally {
setLoadingGetData(false);
}
};
const handlerUpdateData = async () => {
if (!text) {
Toast.show({
type: "error",
text1: "Harap masukkan diskusi",
});
return;
}
try {
setIsLoading(true);
const response = await apiForumUpdate({
id: id as string,
data: text,
});
if (response.success) {
Toast.show({
type: "success",
text1: "Berhasil diupdate",
});
router.back();
}
} catch (error) {
console.log("[ERROR]", error);
} finally {
setIsLoading(false);
}
};
const buttonFooter = () => {
return (
<>
{!loadingGetData && (
<BoxButtonOnFooter>
<ButtonCustom isLoading={isLoading} onPress={handlerUpdateData}>
Update
</ButtonCustom>
</BoxButtonOnFooter>
)}
</>
);
};
return ( return (
<ViewWrapper footerComponent={buttonFooter}> <ViewWrapper footerComponent={buttonFooter()}>
<TextAreaCustom {!loadingGetData ? (
placeholder="Ketik diskusi anda..." <TextAreaCustom
maxLength={1000} placeholder="Ketik diskusi anda..."
showCount maxLength={1000}
value={text} showCount
onChangeText={setText} value={text}
/> onChangeText={(value) => {
setText(value);
}}
/>
) : (
<LoaderCustom />
)}
</ViewWrapper> </ViewWrapper>
); );
} }

View File

@@ -1,35 +1,86 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { import {
AlertCustom, AvatarComp,
AvatarCustom,
ButtonCustom, ButtonCustom,
CenterCustom, CenterCustom,
DrawerCustom, DrawerCustom,
FloatingButton,
Grid, Grid,
LoaderCustom,
StackCustom, StackCustom,
TextCustom, TextCustom,
ViewWrapper, ViewWrapper,
} from "@/components"; } from "@/components";
import { MainColor } from "@/constants/color-palet"; import { useAuth } from "@/hooks/use-auth";
import Forum_BoxDetailSection from "@/screens/Forum/DiscussionBoxSection"; import Forum_BoxDetailSection from "@/screens/Forum/DiscussionBoxSection";
import { listDummyDiscussionForum } from "@/screens/Forum/list-data-dummy";
import Forum_MenuDrawerBerandaSection from "@/screens/Forum/MenuDrawerSection.tsx/MenuBeranda"; import Forum_MenuDrawerBerandaSection from "@/screens/Forum/MenuDrawerSection.tsx/MenuBeranda";
import { useLocalSearchParams } from "expo-router"; import { apiForumGetAll } from "@/service/api-client/api-forum";
import { useState } from "react"; import { apiUser } from "@/service/api-client/api-user";
import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
import _ from "lodash";
import { useCallback, useState } from "react";
export default function Forumku() { export default function Forumku() {
const { id } = useLocalSearchParams(); const { id } = useLocalSearchParams();
const { user } = useAuth();
const [openDrawer, setOpenDrawer] = useState(false); const [openDrawer, setOpenDrawer] = useState(false);
const [status, setStatus] = useState(""); const [status, setStatus] = useState("");
const [alertStatus, setAlertStatus] = useState(false); const [listData, setListData] = useState<any | null>(null);
const [deleteAlert, setDeleteAlert] = useState(false); const [dataUser, setDataUser] = useState<any | null>(null);
const [loadingGetList, setLoadingGetList] = useState(false);
useFocusEffect(
useCallback(() => {
onLoadData();
onLoadDataProfile(id as string);
}, [id])
);
const onLoadDataProfile = async (id: string) => {
try {
const response = await apiUser(id);
setDataUser(response.data);
} catch (error) {
console.log("[ERROR]", error);
} finally {
}
};
const onLoadData = async () => {
try {
setLoadingGetList(true);
const response = await apiForumGetAll({
search: "",
authorId: id as string,
});
setListData(response.data);
} catch (error) {
console.log("[ERROR]", error);
} finally {
setLoadingGetList(false);
}
};
return ( return (
<> <>
<ViewWrapper> <ViewWrapper
floatingButton={
user?.id === id && (
<FloatingButton
onPress={() =>
router.navigate("/(application)/(user)/forum/create")
}
/>
)
}
>
<StackCustom> <StackCustom>
<CenterCustom> <CenterCustom>
<AvatarCustom <AvatarComp
href={`/(application)/(image)/preview-image/${id}`} fileId={dataUser?.Profile?.imageId}
href={`/(application)/(image)/preview-image/${dataUser?.Profile?.imageId}`}
size="xl" size="xl"
/> />
</CenterCustom> </CenterCustom>
@@ -37,32 +88,43 @@ export default function Forumku() {
<Grid> <Grid>
<Grid.Col span={6}> <Grid.Col span={6}>
<TextCustom bold truncate> <TextCustom bold truncate>
@bagas_banuna @{dataUser?.username || "-"}
</TextCustom> </TextCustom>
<TextCustom>1 postingan</TextCustom> <TextCustom>{listData?.length || "0"} postingan</TextCustom>
</Grid.Col> </Grid.Col>
<Grid.Col span={6} style={{ alignItems: "flex-end" }}> <Grid.Col span={6} style={{ alignItems: "flex-end" }}>
<ButtonCustom href={`/profile/${id}`}> <ButtonCustom href={`/profile/${dataUser?.Profile?.id}`}>
Kunjungi Profile Kunjungi Profile
</ButtonCustom> </ButtonCustom>
</Grid.Col> </Grid.Col>
</Grid> </Grid>
{listDummyDiscussionForum.map((e, i) => ( {loadingGetList ? (
<Forum_BoxDetailSection <LoaderCustom />
key={i} ) : _.isEmpty(listData) ? (
data={e} <TextCustom> Tidak ada diskusi</TextCustom>
setOpenDrawer={setOpenDrawer} ) : (
setStatus={setStatus} <>
isTruncate={true} {listData?.map((item: any, index: number) => (
href={`/forum/${id}`} <Forum_BoxDetailSection
/> isRightComponent={false}
))} key={index}
data={item}
isTruncate={true}
href={`/forum/${item.id}`}
onSetData={(value) => {
setOpenDrawer(value.setOpenDrawer);
setStatus(value.setStatus);
}}
/>
))}
</>
)}
</StackCustom> </StackCustom>
</ViewWrapper> </ViewWrapper>
{/* Drawer Komponen Eksternal */} {/* Drawer Komponen Eksternal */}
<DrawerCustom <DrawerCustom
height={350} height={"auto"}
isVisible={openDrawer} isVisible={openDrawer}
closeDrawer={() => setOpenDrawer(false)} closeDrawer={() => setOpenDrawer(false)}
> >
@@ -72,42 +134,9 @@ export default function Forumku() {
setIsDrawerOpen={() => { setIsDrawerOpen={() => {
setOpenDrawer(false); setOpenDrawer(false);
}} }}
setShowDeleteAlert={setDeleteAlert} authorId={id as string}
setShowAlertStatus={setAlertStatus}
/> />
</DrawerCustom> </DrawerCustom>
{/* Alert Komponen Eksternal */}
<AlertCustom
isVisible={alertStatus}
onLeftPress={() => setAlertStatus(false)}
onRightPress={() => {
setOpenDrawer(false);
setAlertStatus(false);
console.log("Ubah status forum");
}}
title="Ubah Status Forum"
message="Apakah Anda yakin ingin mengubah status forum ini?"
textLeft="Batal"
textRight="Ubah"
colorRight={MainColor.green}
/>
{/* Alert Delete */}
<AlertCustom
isVisible={deleteAlert}
onLeftPress={() => setDeleteAlert(false)}
onRightPress={() => {
setOpenDrawer(false);
setDeleteAlert(false);
console.log("Hapus forum");
}}
title="Hapus Forum"
message="Apakah Anda yakin ingin menghapus forum ini?"
textLeft="Batal"
textRight="Hapus"
colorRight={MainColor.red}
/>
</> </>
); );
} }

View File

@@ -1,176 +1,263 @@
import { import {
AlertCustom,
ButtonCustom, ButtonCustom,
DrawerCustom, DrawerCustom,
LoaderCustom,
Spacing, Spacing,
TextAreaCustom, TextAreaCustom,
TextCustom,
ViewWrapper, ViewWrapper,
} from "@/components"; } from "@/components";
import { MainColor } from "@/constants/color-palet"; import { useAuth } from "@/hooks/use-auth";
import Forum_CommentarBoxSection from "@/screens/Forum/CommentarBoxSection"; import Forum_CommentarBoxSection from "@/screens/Forum/CommentarBoxSection";
import Forum_BoxDetailSection from "@/screens/Forum/DiscussionBoxSection"; import Forum_BoxDetailSection from "@/screens/Forum/DiscussionBoxSection";
import { listDummyCommentarForum } from "@/screens/Forum/list-data-dummy";
import Forum_MenuDrawerBerandaSection from "@/screens/Forum/MenuDrawerSection.tsx/MenuBeranda"; import Forum_MenuDrawerBerandaSection from "@/screens/Forum/MenuDrawerSection.tsx/MenuBeranda";
import Forum_MenuDrawerCommentar from "@/screens/Forum/MenuDrawerSection.tsx/MenuCommentar"; import Forum_MenuDrawerCommentar from "@/screens/Forum/MenuDrawerSection.tsx/MenuCommentar";
import { router, useLocalSearchParams } from "expo-router"; import {
import { useState } from "react"; apiForumCreateComment,
apiForumGetComment,
apiForumGetOne,
apiForumUpdateStatus,
} from "@/service/api-client/api-forum";
import { useFocusEffect, useLocalSearchParams } from "expo-router";
import _ from "lodash";
import { useCallback, useEffect, useState } from "react";
interface CommentProps {
id: string;
isActive: boolean;
komentar: string;
createdAt: Date;
authorId: string;
Author: {
id: string;
username: string;
Profile: {
id: string;
imageId: string;
};
};
}
export default function ForumDetail() { export default function ForumDetail() {
const { id } = useLocalSearchParams(); const { id } = useLocalSearchParams();
console.log(id); const { user } = useAuth();
const [openDrawer, setOpenDrawer] = useState(false); const [openDrawer, setOpenDrawer] = useState(false);
const [data, setData] = useState<any | null>(null);
const [listComment, setListComment] = useState<CommentProps[] | null>(null);
const [isLoadingComment, setLoadingComment] = useState(false);
// Status
const [status, setStatus] = useState(""); const [status, setStatus] = useState("");
const [alertStatus, setAlertStatus] = useState(false);
const [deleteAlert, setDeleteAlert] = useState(false);
const [text, setText] = useState(""); const [text, setText] = useState("");
const [authorId, setAuthorId] = useState("");
const [dataId, setDataId] = useState("");
// Comentar // Comentar
const [openDrawerCommentar, setOpenDrawerCommentar] = useState(false); const [openDrawerCommentar, setOpenDrawerCommentar] = useState(false);
const [alertDeleteCommentar, setAlertDeleteCommentar] = useState(false); const [commentId, setCommentId] = useState("");
const [commentAuthorId, setCommentAuthorId] = useState("");
const dataDummy = { useFocusEffect(
name: "Bagas", useCallback(() => {
status: "Open", onLoadData(id as string);
date: "14/07/2025", }, [id])
deskripsi: );
"Lorem ipsum dolor sit amet consectetur adipisicing elit. Vitae inventore iure pariatur, libero omnis excepturi. Ullam ad officiis deleniti quos esse odit nesciunt, ipsam adipisci cumque aliquam corporis culpa fugit?",
jumlahBalas: 2, const onLoadData = async (id: string) => {
try {
const response = await apiForumGetOne({ id });
setData(response.data);
} catch (error) {
console.log("[ERROR]", error);
}
};
useEffect(() => {
onLoadListComment(id as string);
}, [id]);
const onLoadListComment = async (id: string) => {
try {
const response = await apiForumGetComment({
id: id as string,
});
setListComment(response.data);
} catch (error) {
console.log("[ERROR]", error);
}
};
// Update Status
const handlerUpdateStatus = async (value: any) => {
try {
const response = await apiForumUpdateStatus({
id: id as string,
data: value,
});
if (response.success) {
setStatus(response.data);
setData({
...data,
ForumMaster_StatusPosting: {
status: response.data,
},
});
}
} catch (error) {
console.log("[ERROR]", error);
}
};
// Create Commentar
const handlerCreateCommentar = async () => {
const newData = {
comment: text,
authorId: user?.id,
};
try {
setLoadingComment(true);
const response = await apiForumCreateComment({
id: id as string,
data: newData,
});
if (response.success) {
setText("");
const newComment = {
id: response.data.id,
isActive: response.data.isActive,
komentar: response.data.komentar,
createdAt: response.data.createdAt,
authorId: response.data.authorId,
Author: response.data.Author,
};
setListComment((prev) => [newComment, ...(prev || [])]);
setData({
...data,
count: data.count + 1,
});
}
} catch (error) {
console.log("[ERROR]", error);
} finally {
setLoadingComment(false);
}
}; };
return ( return (
<> <>
<ViewWrapper> <ViewWrapper>
{/* <StackCustom> {!data && !listComment ? (
</StackCustom> */} <LoaderCustom />
<Forum_BoxDetailSection ) : (
data={dataDummy} <>
setOpenDrawer={setOpenDrawer} {/* Box Posting */}
setStatus={setStatus} <Forum_BoxDetailSection
/> data={data}
onSetData={() => {
setOpenDrawer(true);
setStatus(data.ForumMaster_StatusPosting?.status);
setAuthorId(data.Author?.id);
setDataId(data.id);
}}
/>
<TextAreaCustom {/* Area Commentar */}
placeholder="Ketik diskusi anda..." {data?.ForumMaster_StatusPosting?.status === "Open" && (
maxLength={1000} <>
showCount <TextAreaCustom
value={text} placeholder="Ketik diskusi anda..."
onChangeText={setText} maxLength={1000}
style={{ showCount
marginBottom: 0, value={text}
}} onChangeText={setText}
/> style={{
<ButtonCustom marginBottom: 0,
style={{ }}
alignSelf: "flex-end", />
}} <ButtonCustom
onPress={() => { isLoading={isLoadingComment}
console.log("Posting", text); style={{
router.back(); alignSelf: "flex-end",
}} }}
> onPress={() => {
Balas handlerCreateCommentar();
</ButtonCustom> }}
>
Balas
</ButtonCustom>
</>
)}
<Spacing height={40} />
<Spacing height={40} /> {/* List Commentar */}
{_.isEmpty(listComment) ? (
{listDummyCommentarForum.map((e, i) => ( <TextCustom align="center" color="gray" size={"small"}>
<Forum_CommentarBoxSection Tidak ada komentar
key={i} </TextCustom>
data={e} ) : (
setOpenDrawer={setOpenDrawerCommentar} <TextCustom color="gray">Komentar :</TextCustom>
/> )}
))} <Spacing height={5} />
{listComment?.map((item: any, index: number) => (
<Forum_CommentarBoxSection
key={index}
data={item}
onSetData={(value) => {
setCommentId(value.setCommentId);
setOpenDrawerCommentar(value.setOpenDrawer);
setCommentAuthorId(value.setCommentAuthorId);
}}
/>
))}
</>
)}
</ViewWrapper> </ViewWrapper>
{/* Posting Drawer */}
<DrawerCustom <DrawerCustom
height={350} height={"auto"}
isVisible={openDrawer} isVisible={openDrawer}
closeDrawer={() => setOpenDrawer(false)} closeDrawer={() => setOpenDrawer(false)}
> >
<Forum_MenuDrawerBerandaSection <Forum_MenuDrawerBerandaSection
id={id as string} id={dataId}
status={status} status={status}
setIsDrawerOpen={() => { setIsDrawerOpen={() => {
setOpenDrawer(false); setOpenDrawer(false);
}} }}
setShowDeleteAlert={setDeleteAlert} authorId={authorId}
setShowAlertStatus={setAlertStatus} handlerUpdateStatus={(value: any) => {
handlerUpdateStatus(value);
}}
/> />
</DrawerCustom> </DrawerCustom>
{/* Alert Status */} {/* Commentar Drawer */}
<AlertCustom
isVisible={alertStatus}
title="Ubah Status Forum"
message="Apakah Anda yakin ingin mengubah status forum ini?"
onLeftPress={() => {
setOpenDrawer(false);
setAlertStatus(false);
console.log("Batal");
}}
onRightPress={() => {
setOpenDrawer(false);
setAlertStatus(false);
console.log("Ubah status forum");
}}
textLeft="Batal"
textRight="Ubah"
colorRight={MainColor.green}
/>
{/* Alert Delete */}
<AlertCustom
isVisible={deleteAlert}
title="Hapus Forum"
message="Apakah Anda yakin ingin menghapus forum ini?"
onLeftPress={() => {
setOpenDrawer(false);
setDeleteAlert(false);
console.log("Batal");
}}
onRightPress={() => {
setOpenDrawer(false);
setDeleteAlert(false);
console.log("Hapus forum");
}}
textLeft="Batal"
textRight="Hapus"
colorRight={MainColor.red}
/>
{/* Commentar */}
<DrawerCustom <DrawerCustom
height={350} height={"auto"}
isVisible={openDrawerCommentar} isVisible={openDrawerCommentar}
closeDrawer={() => setOpenDrawerCommentar(false)} closeDrawer={() => setOpenDrawerCommentar(false)}
> >
<Forum_MenuDrawerCommentar <Forum_MenuDrawerCommentar
id={id as string} id={commentId as string}
commentId={commentId}
commentAuthorId={commentAuthorId}
setIsDrawerOpen={() => { setIsDrawerOpen={() => {
setOpenDrawerCommentar(false); setOpenDrawerCommentar(false);
}} }}
setShowDeleteAlert={setAlertDeleteCommentar} listComment={listComment}
setListComment={setListComment}
countComment={data?.count}
setCountComment={(val: any) => {
setData((prev: any) => ({
...prev,
count: val,
}));
}}
/> />
</DrawerCustom> </DrawerCustom>
{/* Alert Delete Commentar */}
<AlertCustom
isVisible={alertDeleteCommentar}
title="Hapus Komentar"
message="Apakah Anda yakin ingin menghapus komentar ini?"
onLeftPress={() => {
setOpenDrawerCommentar(false);
setAlertDeleteCommentar(false);
console.log("Batal");
}}
onRightPress={() => {
setOpenDrawerCommentar(false);
setAlertDeleteCommentar(false);
console.log("Hapus commentar");
}}
textLeft="Batal"
textRight="Hapus"
colorRight={MainColor.red}
/>
</> </>
); );
} }

View File

@@ -5,27 +5,69 @@ import {
ViewWrapper, ViewWrapper,
} from "@/components"; } from "@/components";
import { MainColor } from "@/constants/color-palet"; import { MainColor } from "@/constants/color-palet";
import { router } from "expo-router"; import { useAuth } from "@/hooks/use-auth";
import { apiForumCreateReportCommentar } from "@/service/api-client/api-master";
import { router, useLocalSearchParams } from "expo-router";
import { useState } from "react";
import Toast from "react-native-toast-message";
export default function ForumOtherReportCommentar() { export default function ForumOtherReportCommentar() {
const { id } = useLocalSearchParams();
const { user } = useAuth();
const [value, setValue] = useState<string>("");
const handlerSubmitReport = async () => {
const newData = {
authorId: user?.id,
description: value,
};
try {
const response = await apiForumCreateReportCommentar({
id: id as string,
data: newData,
});
if (response.success) {
Toast.show({
type: "success",
text1: "Laporan berhasil dikirim",
});
router.back();
}
} catch (error) {
console.log("[ERROR]", error);
Toast.show({
type: "error",
text1: "Gagal",
text2: "Laporan gagal dikirim",
});
}
};
const handleSubmit = ( const handleSubmit = (
<BoxButtonOnFooter> <BoxButtonOnFooter>
<ButtonCustom <ButtonCustom
disabled={!value}
backgroundColor={MainColor.red} backgroundColor={MainColor.red}
textColor={MainColor.white} textColor={MainColor.white}
onPress={() => { onPress={() => {
console.log("Report lainnya"); handlerSubmitReport();
router.back();
}} }}
> >
Report Report
</ButtonCustom> </ButtonCustom>
</BoxButtonOnFooter> </BoxButtonOnFooter>
); );
return ( return (
<> <>
<ViewWrapper footerComponent={handleSubmit}> <ViewWrapper footerComponent={handleSubmit}>
<TextAreaCustom placeholder="Laporkan Komentar" /> <TextAreaCustom
placeholder="Laporkan Komentar"
value={value}
onChangeText={setValue}
/>
</ViewWrapper> </ViewWrapper>
</> </>
); );

View File

@@ -5,17 +5,54 @@ import {
ViewWrapper, ViewWrapper,
} from "@/components"; } from "@/components";
import { MainColor } from "@/constants/color-palet"; import { MainColor } from "@/constants/color-palet";
import { router } from "expo-router"; import { useAuth } from "@/hooks/use-auth";
import { apiForumCreateReportPosting } from "@/service/api-client/api-master";
import { router, useLocalSearchParams } from "expo-router";
import { useState } from "react";
import Toast from "react-native-toast-message";
export default function ForumOtherReportPosting() { export default function ForumOtherReportPosting() {
const { id } = useLocalSearchParams();
const { user } = useAuth();
const [value, setValue] = useState<string>("");
const handlerSubmitReport = async () => {
const newData = {
authorId: user?.id,
description: value,
};
try {
const response = await apiForumCreateReportPosting({
id: id as string,
data: newData,
});
if (response.success) {
Toast.show({
type: "success",
text1: "Laporan berhasil dikirim",
});
router.back();
}
} catch (error) {
console.log("[ERROR]", error);
Toast.show({
type: "error",
text1: "Gagal",
text2: "Laporan gagal dikirim",
});
}
};
const handleSubmit = ( const handleSubmit = (
<BoxButtonOnFooter> <BoxButtonOnFooter>
<ButtonCustom <ButtonCustom
disabled={!value}
backgroundColor={MainColor.red} backgroundColor={MainColor.red}
textColor={MainColor.white} textColor={MainColor.white}
onPress={() => { onPress={() => {
console.log("Report lainnya"); handlerSubmitReport();
router.back();
}} }}
> >
Report Report
@@ -25,7 +62,11 @@ export default function ForumOtherReportPosting() {
return ( return (
<> <>
<ViewWrapper footerComponent={handleSubmit}> <ViewWrapper footerComponent={handleSubmit}>
<TextAreaCustom placeholder="Laporkan Diskusi" /> <TextAreaCustom
placeholder="Laporkan Diskusi"
value={value}
onChangeText={setValue}
/>
</ViewWrapper> </ViewWrapper>
</> </>
); );

View File

@@ -1,41 +1,105 @@
import { import {
ButtonCustom, ButtonCustom,
Spacing, LoaderCustom,
StackCustom, Spacing,
ViewWrapper StackCustom,
ViewWrapper,
} from "@/components"; } from "@/components";
import { AccentColor, MainColor } from "@/constants/color-palet"; import { AccentColor, MainColor } from "@/constants/color-palet";
import { useAuth } from "@/hooks/use-auth";
import Forum_ReportListSection from "@/screens/Forum/ReportListSection"; import Forum_ReportListSection from "@/screens/Forum/ReportListSection";
import { router } from "expo-router"; import { apiForumCreateReportCommentar, apiMasterForumReportList } from "@/service/api-client/api-master";
import { router, useLocalSearchParams } from "expo-router";
import { useState, useEffect } from "react";
import Toast from "react-native-toast-message";
export default function ForumReportCommentar() { export default function ForumReportCommentar() {
const { id } = useLocalSearchParams();
const { user } = useAuth();
const [selectReport, setSelectReport] = useState<string>("");
const [listMaster, setListMaster] = useState<any[] | null>(null);
const [isLoadingList, setIsLoadingList] = useState(false);
useEffect(() => {
onLoadListMaster();
}, []);
const onLoadListMaster = async () => {
try {
setIsLoadingList(true);
const response = await apiMasterForumReportList();
setListMaster(response.data);
} catch (error) {
console.log("[ERROR]", error);
} finally {
setIsLoadingList(false);
}
};
const handlerReport = async () => {
const newData = {
authorId: user?.id,
categoryId: selectReport,
};
try {
const response = await apiForumCreateReportCommentar({
id: id as string,
data: newData,
});
if (response.success) {
Toast.show({
type: "success",
text1: "Laporan berhasil dikirim",
});
router.back();
}
} catch (error) {
console.log("[ERROR]", error);
Toast.show({
type: "error",
text1: "Gagal",
text2: "Laporan gagal dikirim",
});
}
};
return ( return (
<> <>
<ViewWrapper> <ViewWrapper>
<StackCustom> {isLoadingList ? (
<Forum_ReportListSection /> <LoaderCustom />
<ButtonCustom ) : (
backgroundColor={MainColor.red} <StackCustom>
textColor={MainColor.white} <Forum_ReportListSection
onPress={() => { listMaster={listMaster}
console.log("Report"); selectReport={selectReport}
router.back(); setSelectReport={setSelectReport}
}} />
> <ButtonCustom
Report disabled={!selectReport}
</ButtonCustom> backgroundColor={MainColor.red}
<ButtonCustom textColor={MainColor.white}
backgroundColor={AccentColor.blue} onPress={() => {
textColor={MainColor.white} handlerReport();
onPress={() => { }}
console.log("Lainnya"); >
router.replace("/forum/[id]/other-report-commentar"); Report
}} </ButtonCustom>
> <ButtonCustom
Lainnya backgroundColor={AccentColor.blue}
</ButtonCustom> textColor={MainColor.white}
<Spacing/> onPress={() => {
</StackCustom> router.replace(`/forum/${id}/other-report-commentar`);
}}
>
Lainnya
</ButtonCustom>
<Spacing />
</StackCustom>
)}
</ViewWrapper> </ViewWrapper>
</> </>
); );

View File

@@ -1,20 +1,103 @@
import { ViewWrapper, StackCustom, ButtonCustom, Spacing } from "@/components"; import {
import { MainColor, AccentColor } from "@/constants/color-palet"; AlertDefaultSystem,
ButtonCustom,
LoaderCustom,
Spacing,
StackCustom,
ViewWrapper,
} from "@/components";
import { AccentColor, MainColor } from "@/constants/color-palet";
import { useAuth } from "@/hooks/use-auth";
import Forum_ReportListSection from "@/screens/Forum/ReportListSection"; import Forum_ReportListSection from "@/screens/Forum/ReportListSection";
import { router } from "expo-router"; import {
apiForumCreateReportPosting,
apiMasterForumReportList,
} from "@/service/api-client/api-master";
import { router, useLocalSearchParams } from "expo-router";
import { useEffect, useState } from "react";
import Toast from "react-native-toast-message";
export default function ForumReportPosting() { export default function ForumReportPosting() {
return ( const { id } = useLocalSearchParams();
<> const { user } = useAuth();
<ViewWrapper> const [selectReport, setSelectReport] = useState<string>("");
const [listMaster, setListMaster] = useState<any[] | null>(null);
const [isLoadingList, setIsLoadingList] = useState(false);
useEffect(() => {
onLoadListMaster();
}, []);
const onLoadListMaster = async () => {
try {
setIsLoadingList(true);
const response = await apiMasterForumReportList();
setListMaster(response.data);
} catch (error) {
console.log("[ERROR]", error);
} finally {
setIsLoadingList(false);
}
};
const handlerReport = async () => {
const newData = {
authorId: user?.id,
categoryId: selectReport,
};
try {
const response = await apiForumCreateReportPosting({
id: id as string,
data: newData,
});
if (response.success) {
Toast.show({
type: "success",
text1: "Laporan berhasil dikirim",
});
router.back();
}
} catch (error) {
console.log("[ERROR]", error);
Toast.show({
type: "error",
text1: "Gagal",
text2: "Laporan gagal dikirim",
});
}
};
return (
<>
<ViewWrapper>
{isLoadingList ? (
<LoaderCustom />
) : (
<StackCustom> <StackCustom>
<Forum_ReportListSection /> <Forum_ReportListSection
listMaster={listMaster}
selectReport={selectReport}
setSelectReport={setSelectReport}
/>
<ButtonCustom <ButtonCustom
disabled={!selectReport}
backgroundColor={MainColor.red} backgroundColor={MainColor.red}
textColor={MainColor.white} textColor={MainColor.white}
onPress={() => { onPress={() => {
console.log("Report"); AlertDefaultSystem({
router.back(); title: "Laporan Posting",
message: "Apakah anda yakin ingin melaporkan postingan ini?",
textLeft: "Batal",
textRight: "Laporkan",
onPressRight: () => {
handlerReport();
},
});
}} }}
> >
Report Report
@@ -23,15 +106,15 @@ export default function ForumReportPosting() {
backgroundColor={AccentColor.blue} backgroundColor={AccentColor.blue}
textColor={MainColor.white} textColor={MainColor.white}
onPress={() => { onPress={() => {
console.log("Lainnya"); router.replace(`/forum/${id}/other-report-posting`);
router.replace("/forum/[id]/other-report-posting");
}} }}
> >
Lainnya Lainnya
</ButtonCustom> </ButtonCustom>
<Spacing /> <Spacing />
</StackCustom> </StackCustom>
</ViewWrapper> )}
</> </ViewWrapper>
); </>
} );
}

View File

@@ -4,18 +4,47 @@ import {
TextAreaCustom, TextAreaCustom,
ViewWrapper, ViewWrapper,
} from "@/components"; } from "@/components";
import { useAuth } from "@/hooks/use-auth";
import { apiForumCreate } from "@/service/api-client/api-forum";
import { router } from "expo-router"; import { router } from "expo-router";
import { useState } from "react"; import { useState } from "react";
import Toast from "react-native-toast-message";
export default function ForumCreate() { export default function ForumCreate() {
const { user } = useAuth();
const [text, setText] = useState(""); const [text, setText] = useState("");
const [isLoading, setIsLoading] = useState(false);
const handlerSubmit = async () => {
const newData = {
diskusi: text,
authorId: user?.id,
};
try {
setIsLoading(true);
const response = await apiForumCreate({ data: newData });
if (response.success) {
Toast.show({
type: "success",
text1: "Posting berhasil",
});
setText("");
router.back();
}
} catch (error) {
console.log("[ERROR]", error);
} finally {
setIsLoading(false);
}
};
const buttonFooter = ( const buttonFooter = (
<BoxButtonOnFooter> <BoxButtonOnFooter>
<ButtonCustom <ButtonCustom
isLoading={isLoading}
onPress={() => { onPress={() => {
console.log("Posting", text); handlerSubmit();
router.back();
}} }}
> >
Posting Posting

View File

@@ -1,25 +1,58 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { import {
AlertCustom, AvatarComp,
AvatarCustom,
BackButton, BackButton,
DrawerCustom, DrawerCustom,
LoaderCustom,
SearchInput, SearchInput,
TextCustom,
ViewWrapper, ViewWrapper,
} from "@/components"; } from "@/components";
import FloatingButton from "@/components/Button/FloatingButton"; import FloatingButton from "@/components/Button/FloatingButton";
import { MainColor } from "@/constants/color-palet"; import { useAuth } from "@/hooks/use-auth";
import Forum_BoxDetailSection from "@/screens/Forum/DiscussionBoxSection"; import Forum_BoxDetailSection from "@/screens/Forum/DiscussionBoxSection";
import { listDummyDiscussionForum } from "@/screens/Forum/list-data-dummy";
import Forum_MenuDrawerBerandaSection from "@/screens/Forum/MenuDrawerSection.tsx/MenuBeranda"; import Forum_MenuDrawerBerandaSection from "@/screens/Forum/MenuDrawerSection.tsx/MenuBeranda";
import { router, Stack } from "expo-router"; import { apiForumGetAll } from "@/service/api-client/api-forum";
import { useState } from "react"; import { apiUser } from "@/service/api-client/api-user";
import { router, Stack, useFocusEffect } from "expo-router";
import _ from "lodash";
import { useCallback, useState } from "react";
export default function Forum() { export default function Forum() {
const id = "test-id-forum";
const [openDrawer, setOpenDrawer] = useState(false); const [openDrawer, setOpenDrawer] = useState(false);
const [status, setStatus] = useState(""); const [status, setStatus] = useState("");
const [alertStatus, setAlertStatus] = useState(false); const { user } = useAuth();
const [deleteAlert, setDeleteAlert] = useState(false); const [dataUser, setDataUser] = useState<any>();
const [listData, setListData] = useState<any[]>();
const [loadingGetList, setLoadingGetList] = useState(false);
const [search, setSearch] = useState("");
const [dataId, setDataId] = useState("");
const [authorId, setAuthorId] = useState("");
useFocusEffect(
useCallback(() => {
onLoadData();
onLoadDataProfile(user?.id as string);
}, [user?.id, search])
);
const onLoadDataProfile = async (id: string) => {
const response = await apiUser(id);
setDataUser(response.data);
};
const onLoadData = async () => {
try {
setLoadingGetList(true);
const response = await apiForumGetAll({ search: search });
setListData(response.data);
} catch (error) {
console.log("[ERROR]", error);
} finally {
setLoadingGetList(false);
}
};
return ( return (
<> <>
@@ -27,12 +60,23 @@ export default function Forum() {
options={{ options={{
title: "Forum", title: "Forum",
headerLeft: () => <BackButton />, headerLeft: () => <BackButton />,
headerRight: () => <AvatarCustom href={`/forum/${id}/forumku`} />, headerRight: () => (
<AvatarComp
fileId={dataUser?.Profile?.imageId}
size="base"
href={`/forum/${user?.id}/forumku`}
/>
),
}} }}
/> />
<ViewWrapper <ViewWrapper
headerComponent={<SearchInput placeholder="Cari topik diskusi" />} headerComponent={
<SearchInput
placeholder="Cari topik diskusi"
onChangeText={(e) => setSearch(e)}
/>
}
floatingButton={ floatingButton={
<FloatingButton <FloatingButton
onPress={() => onPress={() =>
@@ -41,73 +85,45 @@ export default function Forum() {
/> />
} }
> >
{listDummyDiscussionForum.map((e, i) => ( {loadingGetList ? (
<Forum_BoxDetailSection <LoaderCustom />
key={i} ) : _.isEmpty(listData) ? (
data={e} <TextCustom align="center" color="gray">
setOpenDrawer={setOpenDrawer} Tidak ada diskusi
setStatus={setStatus} </TextCustom>
isTruncate={true} ) : (
href={`/forum/${id}`} listData?.map((e: any, i: number) => (
/> <Forum_BoxDetailSection
))} key={i}
data={e}
onSetData={() => {
setDataId(e.id);
setOpenDrawer(true);
setStatus(e.ForumMaster_StatusPosting?.status);
setAuthorId(e.Author?.id);
}}
isTruncate={true}
href={`/forum/${e.id}`}
isRightComponent={false}
/>
))
)}
</ViewWrapper> </ViewWrapper>
<DrawerCustom <DrawerCustom
height={350} height={"auto"}
isVisible={openDrawer} isVisible={openDrawer}
closeDrawer={() => setOpenDrawer(false)} closeDrawer={() => setOpenDrawer(false)}
> >
<Forum_MenuDrawerBerandaSection <Forum_MenuDrawerBerandaSection
id={id} id={dataId}
authorId={authorId}
status={status} status={status}
setIsDrawerOpen={() => { setIsDrawerOpen={() => {
setOpenDrawer(false); setOpenDrawer(false);
}} }}
setShowDeleteAlert={setDeleteAlert}
setShowAlertStatus={setAlertStatus}
/> />
</DrawerCustom> </DrawerCustom>
{/* Alert Status */}
<AlertCustom
isVisible={alertStatus}
title="Ubah Status Forum"
message="Apakah Anda yakin ingin mengubah status forum ini?"
onLeftPress={() => {
setOpenDrawer(false);
setAlertStatus(false);
console.log("Batal");
}}
onRightPress={() => {
setOpenDrawer(false);
setAlertStatus(false);
console.log("Ubah status forum");
}}
textLeft="Batal"
textRight="Ubah"
colorRight={MainColor.green}
/>
{/* Alert Delete */}
<AlertCustom
isVisible={deleteAlert}
title="Hapus Forum"
message="Apakah Anda yakin ingin menghapus forum ini?"
onLeftPress={() => {
setOpenDrawer(false);
setDeleteAlert(false);
console.log("Batal");
}}
onRightPress={() => {
setOpenDrawer(false);
setDeleteAlert(false);
console.log("Hapus forum");
}}
textLeft="Batal"
textRight="Hapus"
colorRight={MainColor.red}
/>
</> </>
); );
} }

View File

@@ -1,13 +1,48 @@
import { ScrollableCustom, ViewWrapper } from "@/components"; /* eslint-disable react-hooks/exhaustive-deps */
import {
LoaderCustom,
ScrollableCustom,
TextCustom,
ViewWrapper,
} 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 Investment_StatusBox from "@/screens/Invesment/StatusBox"; import Investment_StatusBox from "@/screens/Invesment/StatusBox";
import { useState } from "react"; import { apiInvestmentGetByStatus } from "@/service/api-client/api-investment";
import { useFocusEffect } from "expo-router";
import _ from "lodash";
import { useCallback, useState } from "react";
export default function InvestmentPortofolio() { export default function InvestmentPortofolio() {
const { user } = useAuth();
const [activeCategory, setActiveCategory] = useState<string | null>( const [activeCategory, setActiveCategory] = useState<string | null>(
"publish" "publish"
); );
const [listData, setListData] = useState<any[]>([]);
const [loadingList, setLoadingList] = useState(false);
useFocusEffect(
useCallback(() => {
onLoadData();
}, [user?.id, activeCategory])
);
const onLoadData = async () => {
try {
setLoadingList(true);
const response = await apiInvestmentGetByStatus({
authorId: user?.id as string,
status: activeCategory as string,
});
setListData(response.data);
} catch (error) {
console.log("[ERROR]", error);
} finally {
setLoadingList(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.
@@ -26,14 +61,20 @@ export default function InvestmentPortofolio() {
); );
return ( return (
<ViewWrapper headerComponent={scrollComponent} hideFooter> <ViewWrapper headerComponent={scrollComponent} hideFooter>
{Array.from({ length: 10 }).map((_, index) => ( {loadingList ? (
<Investment_StatusBox <LoaderCustom />
key={index} ) : _.isEmpty(listData) ? (
id={index.toString()} <TextCustom align="center">Tidak ada data {activeCategory}</TextCustom>
status={activeCategory as string} ) : (
href={`/investment/${index}/${activeCategory}/detail`} listData.map((item: any, index: number) => (
/> <Investment_StatusBox
))} key={index}
data={item}
status={activeCategory as string}
href={`/investment/${item.id}/${activeCategory}/detail`}
/>
))
)}
</ViewWrapper> </ViewWrapper>
); );
} }

View File

@@ -1,3 +1,4 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { import {
BackButton, BackButton,
DotButton, DotButton,
@@ -12,16 +13,42 @@ import { ICON_SIZE_MEDIUM } from "@/constants/constans-value";
import Investment_ButtonInvestasiSection from "@/screens/Invesment/ButtonInvestasiSection"; import Investment_ButtonInvestasiSection from "@/screens/Invesment/ButtonInvestasiSection";
import Invesment_ComponentBoxOnBottomDetail from "@/screens/Invesment/ComponentBoxOnBottomDetail"; import Invesment_ComponentBoxOnBottomDetail from "@/screens/Invesment/ComponentBoxOnBottomDetail";
import Invesment_DetailDataPublishSection from "@/screens/Invesment/DetailDataPublishSection"; import Invesment_DetailDataPublishSection from "@/screens/Invesment/DetailDataPublishSection";
import { apiInvestmentGetById } from "@/service/api-client/api-investment";
import { AntDesign, MaterialIcons } from "@expo/vector-icons"; import { AntDesign, MaterialIcons } from "@expo/vector-icons";
import { router, Stack, useLocalSearchParams } from "expo-router"; import {
router,
Stack,
useFocusEffect,
useLocalSearchParams,
} from "expo-router";
import _ from "lodash"; import _ from "lodash";
import { useState } from "react"; import { useCallback, useState } from "react";
export default function InvestmentDetailStatus() { export default function InvestmentDetailStatus() {
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 [data, setData] = useState<any>(null);
useFocusEffect(
useCallback(() => {
onLoadData();
}, [id, status])
);
const onLoadData = async () => {
try {
const response = await apiInvestmentGetById({
id: id as string,
});
// console.log("[DATA]", JSON.stringify(response.data, null, 2));
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);
router.navigate(item.path as any); router.navigate(item.path as any);
@@ -63,6 +90,7 @@ export default function InvestmentDetailStatus() {
<ViewWrapper> <ViewWrapper>
<Invesment_DetailDataPublishSection <Invesment_DetailDataPublishSection
status={status as string} status={status as string}
data={data}
bottomSection={bottomSection} bottomSection={bottomSection}
buttonSection={buttonSection} buttonSection={buttonSection}
/> />

View File

@@ -1,3 +1,4 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { import {
ButtonCenteredOnly, ButtonCenteredOnly,
ButtonCustom, ButtonCustom,
@@ -7,34 +8,192 @@ import {
Spacing, Spacing,
StackCustom, StackCustom,
TextInputCustom, TextInputCustom,
ViewWrapper ViewWrapper,
} from "@/components"; } from "@/components";
import API_STRORAGE from "@/constants/base-url-api-strorage";
import DIRECTORY_ID from "@/constants/directory-id";
import dummyPembagianDeviden from "@/lib/dummy-data/investment/pembagian-deviden"; import dummyPembagianDeviden from "@/lib/dummy-data/investment/pembagian-deviden";
import dummyListPencarianInvestor from "@/lib/dummy-data/investment/pencarian-investor"; import dummyListPencarianInvestor from "@/lib/dummy-data/investment/pencarian-investor";
import dummyPeriodeDeviden from "@/lib/dummy-data/investment/periode-deviden"; import dummyPeriodeDeviden from "@/lib/dummy-data/investment/periode-deviden";
import { router } from "expo-router"; import {
import { useState } from "react"; apiInvestmentGetById,
apiInvestmentUpdateData,
} from "@/service/api-client/api-investment";
import {
deleteImageService,
uploadImageService,
} from "@/service/upload-service";
import { formatCurrencyDisplay } from "@/utils/formatCurrencyDisplay";
import pickFile from "@/utils/pickFile";
import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
import { useCallback, useState } from "react";
import Toast from "react-native-toast-message";
interface IInvestment {
title?: string;
targetDana?: string;
hargaLembar?: string;
totalLembar?: string;
roi?: string;
masterPencarianInvestorId?: string;
masterPeriodeDevidenId?: string;
masterPembagianDevidenId?: string;
authorId?: string;
imageId?: string;
prospektusFileId?: string;
}
export default function InvestmentEdit() { export default function InvestmentEdit() {
const [data, setData] = useState({ const { id } = useLocalSearchParams();
const [data, setData] = useState<IInvestment>({
title: "", title: "",
targetDana: 0, targetDana: "",
hargaPerLembar: 0, hargaLembar: "",
totalLembar: 0, totalLembar: "",
rasioKeuntungan: 0, roi: "",
pencarianInvestor: "", masterPencarianInvestorId: "",
periodeDeviden: "", masterPeriodeDevidenId: "",
pembagianDeviden: "", masterPembagianDevidenId: "",
authorId: "",
imageId: "",
prospektusFileId: "",
}); });
const [image, setImage] = useState<string | null>(null);
const [isLoading, setIsLoading] = useState(false);
const displayTargetDana = formatCurrencyDisplay(data?.targetDana);
const displayHargaPerLembar = formatCurrencyDisplay(data?.hargaLembar);
const displayTotalLembar = formatCurrencyDisplay(
Number(data?.targetDana) / Number(data?.hargaLembar)
);
const handleChangeCurrency = (field: keyof typeof data) => (text: string) => {
const numeric = text.replace(/\D/g, "");
setData((prev) => ({ ...prev, [field]: numeric }));
};
useFocusEffect(
useCallback(() => {
onLoadData();
}, [id])
);
const onLoadData = async () => {
try {
const response = await apiInvestmentGetById({
id: id as string,
});
// console.log("[DATA]", JSON.stringify(response.data, null, 2));
setData(response.data);
} catch (error) {
console.log("[ERROR]", error);
}
};
const handleSubmitUpdate = async () => {
let newData = {
...data,
};
if (
newData?.title === "" ||
newData?.targetDana === "" ||
newData?.hargaLembar === "" ||
newData?.totalLembar === "" ||
newData?.roi === "" ||
newData?.masterPencarianInvestorId === "" ||
newData?.masterPeriodeDevidenId === "" ||
newData?.masterPembagianDevidenId === ""
) {
Toast.show({
type: "info",
text1: "Harap isi semua data",
});
return;
}
try {
setIsLoading(true);
if (image) {
const responseUploadImage = await uploadImageService({
imageUri: image,
dirId: DIRECTORY_ID.investasi_image,
});
if (!responseUploadImage.success) {
Toast.show({
type: "error",
text1: "Gagal mengunggah gambar",
});
return;
}
const deletePrevImage = await deleteImageService({
id: data?.imageId as any,
});
if (!deletePrevImage.success) {
Toast.show({
type: "error",
text1: "Gagal menghapus gambar",
});
return;
}
newData = {
...newData,
imageId: responseUploadImage.data.id,
};
}
const responseUpdate = await apiInvestmentUpdateData({
id: id as string,
data: newData,
});
console.log("[RESPONSE UPDATE]", JSON.parse(JSON.stringify(responseUpdate)));
if (responseUpdate.success) {
Toast.show({
type: "success",
text1: "Data berhasil diupdate",
});
router.back();
} else {
Toast.show({
type: "error",
text1: responseUpdate.message,
});
}
} catch (error) {
console.log("[ERROR]", error);
} finally {
setIsLoading(false);
}
};
return ( return (
<ViewWrapper> <ViewWrapper>
<StackCustom gap={"xs"}> <StackCustom gap={"xs"}>
<InformationBox text="Gambar investasi bisa berupa ilustrasi, poster atau foto terkait investasi." /> <InformationBox text="Gambar investasi bisa berupa ilustrasi, poster atau foto terkait investasi." />
<LandscapeFrameUploaded /> <LandscapeFrameUploaded
image={
image ? image : API_STRORAGE.GET({ fileId: data?.imageId as any })
}
/>
<ButtonCenteredOnly <ButtonCenteredOnly
icon="upload" icon="upload"
onPress={() => router.push("/take-picture/1")} onPress={() => {
pickFile({
setImageUri: ({ uri }) => {
console.log("URI IMAGE", uri);
setImage(uri);
},
allowedType: "image",
});
}}
> >
Upload Upload
</ButtonCenteredOnly> </ButtonCenteredOnly>
@@ -47,7 +206,7 @@ export default function InvestmentEdit() {
required required
placeholder="Judul" placeholder="Judul"
label="Judul" label="Judul"
value={data.title} value={data?.title}
onChangeText={(value) => setData({ ...data, title: value })} onChangeText={(value) => setData({ ...data, title: value })}
/> />
@@ -57,22 +216,8 @@ export default function InvestmentEdit() {
placeholder="0" placeholder="0"
label="Target Dana" label="Target Dana"
keyboardType="numeric" keyboardType="numeric"
onChangeText={(value) => onChangeText={handleChangeCurrency("targetDana")}
setData({ ...data, targetDana: Number(value) }) value={displayTargetDana}
}
value={data.targetDana === 0 ? "" : data.targetDana.toString()}
/>
<TextInputCustom
required
iconLeft="Rp."
placeholder="0"
label="Target Dana"
keyboardType="numeric"
onChangeText={(value) =>
setData({ ...data, targetDana: Number(value) })
}
value={data.targetDana === 0 ? "" : data.targetDana.toString()}
/> />
<TextInputCustom <TextInputCustom
@@ -81,10 +226,8 @@ export default function InvestmentEdit() {
placeholder="0" placeholder="0"
label="Harga Per Lembar" label="Harga Per Lembar"
keyboardType="numeric" keyboardType="numeric"
onChangeText={(value) => onChangeText={handleChangeCurrency("hargaLembar")}
setData({ ...data, targetDana: Number(value) }) value={displayHargaPerLembar}
}
value={data.targetDana === 0 ? "" : data.targetDana.toString()}
/> />
<TextInputCustom <TextInputCustom
@@ -92,10 +235,8 @@ export default function InvestmentEdit() {
placeholder="0" placeholder="0"
label="Total Lembar" label="Total Lembar"
keyboardType="numeric" keyboardType="numeric"
onChangeText={(value) => onChangeText={(value) => setData({ ...data, totalLembar: value })}
setData({ ...data, totalLembar: Number(value) }) value={displayTotalLembar}
}
value={data.totalLembar === 0 ? "" : data.totalLembar.toString()}
/> />
<TextInputCustom <TextInputCustom
@@ -104,12 +245,8 @@ export default function InvestmentEdit() {
label="Rasio Keuntungan / ROI %" label="Rasio Keuntungan / ROI %"
placeholder="0" placeholder="0"
keyboardType="numeric" keyboardType="numeric"
onChangeText={(value) => onChangeText={(value) => setData({ ...data, roi: value })}
setData({ ...data, rasioKeuntungan: Number(value) }) value={data?.roi === "" ? "" : data?.roi}
}
value={
data.rasioKeuntungan === 0 ? "" : data.rasioKeuntungan.toString()
}
/> />
<SelectCustom <SelectCustom
@@ -121,9 +258,9 @@ export default function InvestmentEdit() {
value: item.id, value: item.id,
}))} }))}
onChange={(value) => onChange={(value) =>
setData({ ...data, pencarianInvestor: value as any }) setData({ ...data, masterPencarianInvestorId: value as any })
} }
value={data.pencarianInvestor} value={data.masterPencarianInvestorId}
/> />
<SelectCustom <SelectCustom
@@ -135,9 +272,9 @@ export default function InvestmentEdit() {
value: item.id, value: item.id,
}))} }))}
onChange={(value) => onChange={(value) =>
setData({ ...data, periodeDeviden: value as any }) setData({ ...data, masterPeriodeDevidenId: value as any })
} }
value={data.periodeDeviden} value={data.masterPeriodeDevidenId}
/> />
<SelectCustom <SelectCustom
@@ -149,12 +286,12 @@ export default function InvestmentEdit() {
value: item.id, value: item.id,
}))} }))}
onChange={(value) => onChange={(value) =>
setData({ ...data, pembagianDeviden: value as any }) setData({ ...data, masterPembagianDevidenId: value as any })
} }
value={data.pembagianDeviden} value={data.masterPembagianDevidenId}
/> />
<Spacing /> <Spacing />
<ButtonCustom onPress={() => router.replace("/investment/portofolio")}> <ButtonCustom isLoading={isLoading} onPress={handleSubmitUpdate}>
Simpan Simpan
</ButtonCustom> </ButtonCustom>
</StackCustom> </StackCustom>

View File

@@ -1,76 +1,203 @@
import { import {
BaseBox, BaseBox,
ButtonCenteredOnly, ButtonCenteredOnly,
ButtonCustom, ButtonCustom,
CenterCustom, CenterCustom,
InformationBox, InformationBox,
LandscapeFrameUploaded, LandscapeFrameUploaded,
SelectCustom, SelectCustom,
Spacing, Spacing,
StackCustom, StackCustom,
TextInputCustom, TextCustom,
ViewWrapper, TextInputCustom,
ViewWrapper,
} from "@/components"; } from "@/components";
import { MainColor } from "@/constants/color-palet"; import { MainColor } from "@/constants/color-palet";
import DIRECTORY_ID from "@/constants/directory-id";
import { useAuth } from "@/hooks/use-auth";
import dummyPembagianDeviden from "@/lib/dummy-data/investment/pembagian-deviden"; import dummyPembagianDeviden from "@/lib/dummy-data/investment/pembagian-deviden";
import dummyListPencarianInvestor from "@/lib/dummy-data/investment/pencarian-investor"; import dummyListPencarianInvestor from "@/lib/dummy-data/investment/pencarian-investor";
import dummyPeriodeDeviden from "@/lib/dummy-data/investment/periode-deviden"; import dummyPeriodeDeviden from "@/lib/dummy-data/investment/periode-deviden";
import { apiInvestmentCreate } from "@/service/api-client/api-investment";
import { uploadImageService } from "@/service/upload-service";
import { formatCurrencyDisplay } from "@/utils/formatCurrencyDisplay";
import pickFile, { IFileData } from "@/utils/pickFile";
import { FontAwesome5 } from "@expo/vector-icons"; import { FontAwesome5 } from "@expo/vector-icons";
import { router } from "expo-router"; import { router } from "expo-router";
import { useState } from "react"; import { useState } from "react";
import Toast from "react-native-toast-message";
export default function InvestmentCreate() { export default function InvestmentCreate() {
const { user } = useAuth();
const [data, setData] = useState({ const [data, setData] = useState({
title: "", title: "",
targetDana: 0, targetDana: "",
hargaPerLembar: 0, hargaPerLembar: "",
totalLembar: 0, totalLembar: "",
rasioKeuntungan: 0, rasioKeuntungan: "",
pencarianInvestor: "", pencarianInvestor: "",
periodeDeviden: "", periodeDeviden: "",
pembagianDeviden: "", pembagianDeviden: "",
authorId: "",
imageId: "",
prospektusFileId: "",
}); });
const [image, setImage] = useState<string | null>(null);
const [pdf, setPdf] = useState<IFileData | null>(null);
const [isLoading, setIsLoading] = useState(false);
// const [coba, setCoba] = useState(""); const displayTargetDana = formatCurrencyDisplay(data.targetDana);
const displayHargaPerLembar = formatCurrencyDisplay(data.hargaPerLembar);
const displayTotalLembar = formatCurrencyDisplay(
Number(data.targetDana) / Number(data.hargaPerLembar)
);
const handleChangeCurrency = (field: keyof typeof data) => (text: string) => {
const numeric = text.replace(/\D/g, "");
setData((prev) => ({ ...prev, [field]: numeric }));
};
const handleSubmit = async () => {
if (!image || !pdf) {
Toast.show({
type: "error",
text1: "Harap pilih gambar dan file PDF",
});
return;
}
if (
!data.title ||
!data.targetDana ||
!data.hargaPerLembar ||
!data.rasioKeuntungan ||
!data.pencarianInvestor ||
!data.periodeDeviden ||
!data.pembagianDeviden
) {
Toast.show({
type: "error",
text1: "Harap isi semua data",
});
return;
}
try {
setIsLoading(true);
const responseUploadImage = await uploadImageService({
imageUri: image,
dirId: DIRECTORY_ID.investasi_image,
});
if (!responseUploadImage.success) {
Toast.show({
type: "error",
text1: "Gagal mengunggah gambar",
});
return;
}
const imageId = responseUploadImage.data.id;
const responseUploadPdf = await uploadImageService({
imageUri: pdf.uri,
dirId: DIRECTORY_ID.investasi_prospektus,
});
if (!responseUploadPdf.success) {
Toast.show({
type: "error",
text1: "Gagal mengunggah file PDF",
});
return;
}
const pdfId = responseUploadPdf.data.id;
const newData = {
title: data.title,
targetDana: data.targetDana,
hargaLembar: data.hargaPerLembar,
totalLembar: displayTotalLembar,
roi: data.rasioKeuntungan,
masterPencarianInvestorId: data.pencarianInvestor,
masterPembagianDevidenId: data.pembagianDeviden,
masterPeriodeDevidenId: data.periodeDeviden,
authorId: user?.id,
imageId: imageId,
prospektusFileId: pdfId,
};
const response = await apiInvestmentCreate({ data: newData });
console.log("[RESPONSE]", JSON.stringify(response, null, 2));
if (response.success) {
Toast.show({
type: "success",
text1: "Berhasil",
text2: response.message,
});
router.replace("/investment/portofolio");
} else {
Toast.show({
type: "error",
text1: "Info",
text2: response.message,
});
}
} catch (error) {
console.log("error", error);
} finally {
setIsLoading(false);
}
};
// const [coba, setCoba] = useState("");
return ( return (
<ViewWrapper> <ViewWrapper>
<StackCustom gap={"xs"}> <StackCustom gap={"xs"}>
{/* <View style={GStyles.inputContainerInput}>
<TextInput
style={{
...GStyles.inputText,
}}
onChangeText={(value) => setCoba(value)}
value={coba}
keyboardType="decimal-pad"
/>
</View> */}
<InformationBox text="Gambar investasi bisa berupa ilustrasi, poster atau foto terkait investasi." /> <InformationBox text="Gambar investasi bisa berupa ilustrasi, poster atau foto terkait investasi." />
<LandscapeFrameUploaded /> <LandscapeFrameUploaded image={image as string} />
<ButtonCenteredOnly <ButtonCenteredOnly
icon="upload" icon="upload"
onPress={() => router.push("/take-picture/1")} onPress={() => {
pickFile({
setImageUri: ({ uri }) => {
console.log("URI IMAGE", uri);
setImage(uri);
},
allowedType: "image",
});
}}
> >
Upload Upload
</ButtonCenteredOnly> </ButtonCenteredOnly>
<Spacing /> <Spacing />
<InformationBox text="File prospektus wajib untuk diupload, agar calon investor paham dengan prospek investasi yang akan anda jalankan kedepannya." /> <InformationBox text="File prospektus wajib untuk diupload, agar calon investor paham dengan prospek investasi yang akan anda jalankan kedepannya. Gunakan format PDF." />
<BaseBox> <BaseBox>
<CenterCustom> <CenterCustom>
<FontAwesome5 {pdf ? (
name="file-pdf" <TextCustom>{pdf.name}</TextCustom>
size={30} ) : (
color={MainColor.disabled} <FontAwesome5
/> name="file-pdf"
size={30}
color={MainColor.disabled}
/>
)}
</CenterCustom> </CenterCustom>
</BaseBox> </BaseBox>
<ButtonCenteredOnly <ButtonCenteredOnly
icon="upload" icon="upload"
onPress={() => router.push("/take-picture/1")} onPress={() => {
pickFile({
setPdfUri: ({ uri, name, size }) => {
console.log("URI PDF", JSON.stringify(uri, null, 2));
setPdf({ uri, name, size });
},
allowedType: "pdf",
});
}}
> >
Upload File Upload File
</ButtonCenteredOnly> </ButtonCenteredOnly>
@@ -90,22 +217,8 @@ export default function InvestmentCreate() {
placeholder="0" placeholder="0"
label="Target Dana" label="Target Dana"
keyboardType="numeric" keyboardType="numeric"
onChangeText={(value) => onChangeText={handleChangeCurrency("targetDana")}
setData({ ...data, targetDana: Number(value) }) value={displayTargetDana}
}
value={data.targetDana === 0 ? "" : data.targetDana.toString()}
/>
<TextInputCustom
required
iconLeft="Rp."
placeholder="0"
label="Target Dana"
keyboardType="numeric"
onChangeText={(value) =>
setData({ ...data, targetDana: Number(value) })
}
value={data.targetDana === 0 ? "" : data.targetDana.toString()}
/> />
<TextInputCustom <TextInputCustom
@@ -114,22 +227,24 @@ export default function InvestmentCreate() {
placeholder="0" placeholder="0"
label="Harga Per Lembar" label="Harga Per Lembar"
keyboardType="numeric" keyboardType="numeric"
onChangeText={(value) => onChangeText={handleChangeCurrency("hargaPerLembar")}
setData({ ...data, targetDana: Number(value) }) value={displayHargaPerLembar}
}
value={data.targetDana === 0 ? "" : data.targetDana.toString()}
/> />
<TextInputCustom <StackCustom gap={0}>
required <TextInputCustom
placeholder="0" required
label="Total Lembar" placeholder="0"
keyboardType="numeric" label="Total Lembar"
onChangeText={(value) => keyboardType="numeric"
setData({ ...data, totalLembar: Number(value) }) // onChangeText={handleChangeCurrency("totalLembar")}
} value={displayTotalLembar}
value={data.totalLembar === 0 ? "" : data.totalLembar.toString()} />
/> <TextCustom size={"small"} color="gray">
*Total lembar dihitung dari, Target Dana / Harga Perlembar
</TextCustom>
</StackCustom>
<Spacing />
<TextInputCustom <TextInputCustom
required required
@@ -137,11 +252,9 @@ export default function InvestmentCreate() {
label="Rasio Keuntungan / ROI %" label="Rasio Keuntungan / ROI %"
placeholder="0" placeholder="0"
keyboardType="numeric" keyboardType="numeric"
onChangeText={(value) => onChangeText={(value) => setData({ ...data, rasioKeuntungan: value })}
setData({ ...data, rasioKeuntungan: Number(value) })
}
value={ value={
data.rasioKeuntungan === 0 ? "" : data.rasioKeuntungan.toString() data.rasioKeuntungan === "" ? "" : data.rasioKeuntungan.toString()
} }
/> />
@@ -187,7 +300,7 @@ export default function InvestmentCreate() {
value={data.pembagianDeviden} value={data.pembagianDeviden}
/> />
<Spacing /> <Spacing />
<ButtonCustom onPress={() => router.replace("/investment/portofolio")}> <ButtonCustom isLoading={isLoading} onPress={() => handleSubmit()}>
Simpan Simpan
</ButtonCustom> </ButtonCustom>
</StackCustom> </StackCustom>

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

View File

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

View File

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

View File

@@ -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,23 +1,63 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { import {
AlertDefaultSystem, AlertDefaultSystem,
BackButton, BackButton,
BaseBox,
DotButton, DotButton,
DrawerCustom, DrawerCustom,
LoaderCustom,
MenuDrawerDynamicGrid, MenuDrawerDynamicGrid,
Spacing, Spacing,
TextCustom,
ViewWrapper, ViewWrapper,
} from "@/components"; } from "@/components";
import { IconArchive, IconContribution, IconEdit } from "@/components/_Icon"; import { IconArchive, IconContribution, IconEdit } from "@/components/_Icon";
import { IMenuDrawerItem } from "@/components/_Interface/types"; import { IMenuDrawerItem } from "@/components/_Interface/types";
import Voting_BoxDetailHasilVotingSection from "@/screens/Voting/BoxDetailHasilVotingSection";
import { Voting_BoxDetailSection } from "@/screens/Voting/BoxDetailSection"; import { Voting_BoxDetailSection } from "@/screens/Voting/BoxDetailSection";
import Voting_ButtonStatusSection from "@/screens/Voting/ButtonStatusSection"; import Voting_ButtonStatusSection from "@/screens/Voting/ButtonStatusSection";
import { router, Stack, useLocalSearchParams } from "expo-router"; import {
import { useState } from "react"; apiVotingGetOne,
apiVotingUpdateData,
} from "@/service/api-client/api-voting";
import {
router,
Stack,
useFocusEffect,
useLocalSearchParams,
} from "expo-router";
import { useCallback, useState } from "react";
import Toast from "react-native-toast-message";
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 [loadingGetData, setLoadingGetData] = useState(false);
const [data, setData] = useState<any>(null);
useFocusEffect(
useCallback(() => {
onLoadData();
}, [id])
);
const onLoadData = async () => {
try {
setLoadingGetData(true);
const response = await apiVotingGetOne({ id: id as string });
if (response.success) {
setData(response.data);
}
} catch (error) {
console.log("[ERROR]", error);
} finally {
setLoadingGetData(false);
}
};
const handlePressDraft = (item: IMenuDrawerItem) => { const handlePressDraft = (item: IMenuDrawerItem) => {
console.log("PATH >> ", item.path); console.log("PATH >> ", item.path);
@@ -32,9 +72,24 @@ export default function VotingDetailStatus() {
message: "Apakah Anda yakin ingin mengarsipkan voting ini?", message: "Apakah Anda yakin ingin mengarsipkan voting ini?",
textLeft: "Batal", textLeft: "Batal",
textRight: "Ya", textRight: "Ya",
onPressRight: () => { onPressRight: async () => {
console.log("Hapus"); try {
router.back(); const response = await apiVotingUpdateData({
id: id as string,
data: data.isArsip ? false : true,
category: "archive",
});
if (response.success) {
Toast.show({
type: "success",
text1: response.message,
});
router.back();
}
} catch (error) {
console.log("[ERROR]", error);
}
}, },
}); });
} }
@@ -46,7 +101,7 @@ export default function VotingDetailStatus() {
<> <>
<Stack.Screen <Stack.Screen
options={{ options={{
title: `Detail ${status}`, title: `Detail`,
headerLeft: () => <BackButton />, headerLeft: () => <BackButton />,
headerRight: () => headerRight: () =>
status === "draft" ? ( status === "draft" ? (
@@ -57,9 +112,37 @@ export default function VotingDetailStatus() {
}} }}
/> />
<ViewWrapper> <ViewWrapper>
<Voting_BoxDetailSection /> {loadingGetData ? (
<Voting_ButtonStatusSection status={status as string} /> <LoaderCustom />
<Spacing /> ) : (
<>
{status === "publish" && (
<BaseBox>
<TextCustom bold>
Status:{" "}
<TextCustom color={data?.isArsip ? "red" : "green"}>
{data?.isArsip ? "Arsip" : "Publish"}
</TextCustom>
</TextCustom>
</BaseBox>
)}
<Spacing height={0} />
<Voting_BoxDetailSection data={data as any} />
{status === "publish" ? (
<Voting_BoxDetailHasilVotingSection
listData={data?.Voting_DaftarNamaVote}
/>
) : (
<Voting_ButtonStatusSection
isLoading={isLoading}
onSetLoading={setIsLoading}
id={id as string}
status={status as string}
/>
)}
<Spacing />
</>
)}
</ViewWrapper> </ViewWrapper>
{/* ========= Draft Drawer ========= */} {/* ========= Draft Drawer ========= */}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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>

588
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,4 +1,4 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
// DateTimeInput.tsx // DateTimeInput.tsx
import { MainColor } from "@/constants/color-palet"; import { MainColor } from "@/constants/color-palet";
import { GStyles } from "@/styles/global-styles"; import { GStyles } from "@/styles/global-styles";
@@ -119,7 +119,7 @@ const DateTimeInput_Android: React.FC<DateTimeInputProps> = ({
<Ionicons <Ionicons
name="calendar-outline" name="calendar-outline"
size={20} size={20}
color={MainColor.placeholder} color={disabled ? MainColor.white_gray : MainColor.placeholder}
/> />
</View> </View>
@@ -131,8 +131,8 @@ const DateTimeInput_Android: React.FC<DateTimeInputProps> = ({
}} }}
> >
<Grid.Col span={6} style={{}}> <Grid.Col span={6} style={{}}>
<Pressable onPress={toggleDatePicker}> <Pressable onPress={() => !disabled && toggleDatePicker()}>
<TextCustom color="gray"> <TextCustom color={disabled ? "default" : "gray"}>
{selectedDate ? ( {selectedDate ? (
<TextCustom color="black"> <TextCustom color="black">
{selectedDate.toLocaleDateString()} {selectedDate.toLocaleDateString()}
@@ -148,8 +148,8 @@ const DateTimeInput_Android: React.FC<DateTimeInputProps> = ({
</Grid.Col> </Grid.Col>
<Grid.Col span={5} style={{}}> <Grid.Col span={5} style={{}}>
<Pressable onPress={toggleTimePicker}> <Pressable onPress={() => !disabled && toggleTimePicker()}>
<TextCustom color="gray"> <TextCustom color={disabled ? "default" : "gray"}>
{selectedTime ? ( {selectedTime ? (
<TextCustom color="black"> <TextCustom color="black">
{selectedTime.toLocaleTimeString("id-ID", { {selectedTime.toLocaleTimeString("id-ID", {

View File

@@ -3,24 +3,19 @@ import { MainColor } from "@/constants/color-palet";
import { GStyles } from "@/styles/global-styles"; import { GStyles } from "@/styles/global-styles";
import { Ionicons } from "@expo/vector-icons"; import { Ionicons } from "@expo/vector-icons";
import DateTimePicker, { import DateTimePicker, {
DateTimePickerEvent, DateTimePickerEvent,
} from "@react-native-community/datetimepicker"; } from "@react-native-community/datetimepicker";
import dayjs from "dayjs"; import dayjs from "dayjs";
import React, { useState } from "react"; import React, { useState } from "react";
import { import { StyleProp, Text, View, ViewStyle } from "react-native";
StyleProp,
Text,
View,
ViewStyle
} from "react-native";
import ClickableCustom from "../Clickable/ClickableCustom"; import ClickableCustom from "../Clickable/ClickableCustom";
import TextCustom from "../Text/TextCustom"; import TextCustom from "../Text/TextCustom";
interface DateTimeInputProps { interface DateTimeInputProps {
// Main // Main
value?: DateTimePickerEvent; value?: DateTimePickerEvent | Date | null;
mode?: "date" | "time"; mode?: "date" | "time";
onChange: (selectedDate: DateTimePickerEvent) => void; onChange: (selectedDate: DateTimePickerEvent | Date | null) => void;
maximumDate?: Date; maximumDate?: Date;
minimumDate?: Date; minimumDate?: Date;
// Main // Main
@@ -74,7 +69,7 @@ const DateTimeInput_IOS: React.FC<DateTimeInputProps> = ({
<ClickableCustom <ClickableCustom
activeOpacity={0.8} activeOpacity={0.8}
style={[GStyles.inputContainerArea, containerStyle]} style={[GStyles.inputContainerArea, containerStyle]}
onPress={handlePress} onPress={() => !disabled && handlePress()}
> >
{label && ( {label && (
<Text style={GStyles.inputLabel}> <Text style={GStyles.inputLabel}>
@@ -95,11 +90,11 @@ const DateTimeInput_IOS: React.FC<DateTimeInputProps> = ({
<Ionicons <Ionicons
name="calendar-outline" name="calendar-outline"
size={20} size={20}
color={MainColor.placeholder} color={disabled ? MainColor.white : MainColor.placeholder}
/> />
</View> </View>
<TextCustom color="gray"> <TextCustom color={disabled ? "default" : "gray"}>
{selectedDate ? ( {selectedDate ? (
<TextCustom color="black"> <TextCustom color="black">
{dayjs(selectedDate).format("DD-MM-YYYY HH:mm")} {dayjs(selectedDate).format("DD-MM-YYYY HH:mm")}

View File

@@ -1,19 +1,17 @@
import { DateTimePickerEvent } from "@react-native-community/datetimepicker";
import {
DateTimePickerEvent,
} from "@react-native-community/datetimepicker";
import React from "react"; import React from "react";
import { Platform } from "react-native"; import { Platform } from "react-native";
import DateTimeInput_Android from "./DataTimeAndroid"; import DateTimeInput_Android from "./DataTimeAndroid";
import DateTimeInput_IOS from "./DateTimeIOS"; import DateTimeInput_IOS from "./DateTimeIOS";
type Props = { type Props = {
value?: Date; value?: Date | DateTimePickerEvent | null;
onChange?: (date: Date) => void; onChange?: (date: Date) => void;
label?: string; label?: string;
required?: boolean; required?: boolean;
maximumDate?: Date; maximumDate?: Date;
minimumDate?: Date; minimumDate?: Date;
disabled?: boolean;
}; };
const DateTimePickerCustom: React.FC<Props> = ({ const DateTimePickerCustom: React.FC<Props> = ({
@@ -23,18 +21,21 @@ const DateTimePickerCustom: React.FC<Props> = ({
required, required,
maximumDate, maximumDate,
minimumDate, minimumDate,
disabled = false,
}) => { }) => {
return ( return (
<> <>
{Platform.OS === "ios" ? ( {Platform.OS === "ios" ? (
<DateTimeInput_IOS <DateTimeInput_IOS
label={label} label={label}
onChange={(date: DateTimePickerEvent) => { onChange={(date: DateTimePickerEvent | Date | null) => {
onChange?.(date as any); onChange?.(date as any);
}} }}
required={required} required={required}
maximumDate={maximumDate} maximumDate={maximumDate}
minimumDate={minimumDate} minimumDate={minimumDate}
disabled={disabled}
value={value as DateTimePickerEvent | Date | null}
/> />
) : ( ) : (
<DateTimeInput_Android <DateTimeInput_Android
@@ -45,6 +46,7 @@ const DateTimePickerCustom: React.FC<Props> = ({
required={required} required={required}
maximumDate={maximumDate} maximumDate={maximumDate}
minimumDate={minimumDate} minimumDate={minimumDate}
disabled={disabled}
/> />
)} )}
</> </>

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

@@ -0,0 +1,92 @@
import { AccentColor, MainColor } from "@/constants/color-palet";
import { TEXT_SIZE_LARGE } from "@/constants/constans-value";
import React from "react";
import {
Keyboard,
StyleSheet,
TouchableWithoutFeedback,
View,
} from "react-native";
interface AlertCustomProps {
children: React.ReactNode;
isVisible: boolean;
}
export default function ModalCustom({
children,
isVisible,
}: AlertCustomProps) {
if (!isVisible) return null;
return (
<TouchableWithoutFeedback onPress={Keyboard.dismiss}>
<View style={styles.overlay}>
<View style={styles.alertBox}>
<View style={{ width: "100%" }}>{children}</View>
</View>
</View>
</TouchableWithoutFeedback>
);
}
const styles = StyleSheet.create({
overlay: {
position: "absolute",
top: 0,
left: 0,
right: 0,
bottom: 0,
backgroundColor: "rgba(0,0,0,0.5)",
justifyContent: "center",
alignItems: "center",
zIndex: 999,
paddingVertical: 20,
},
alertBox: {
width: "90%",
backgroundColor: MainColor.darkblue,
borderColor: AccentColor.blue,
borderWidth: 1,
borderRadius: 10,
padding: 20,
alignItems: "center",
shadowColor: "#000",
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.25,
elevation: 5,
},
alertTitle: {
fontSize: TEXT_SIZE_LARGE,
fontWeight: "bold",
marginBottom: 20,
color: MainColor.white_gray,
},
alertMessage: {
textAlign: "center",
marginBottom: 20,
color: MainColor.white_gray,
},
alertButtons: {
flexDirection: "row",
justifyContent: "space-between",
width: "100%",
},
alertButton: {
flex: 1,
padding: 10,
borderRadius: 50,
marginHorizontal: 5,
alignItems: "center",
},
leftButton: {
backgroundColor: "gray",
},
rightButton: {
backgroundColor: MainColor.green,
},
buttonText: {
color: "white",
fontWeight: "bold",
},
});

View File

@@ -1,7 +1,7 @@
import { ImageSourcePropType } from "react-native"; import { AccentColor } from "@/constants/color-palet";
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 +12,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 +21,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}
@@ -40,7 +40,7 @@ const AvatarUsernameAndOtherComponent = ({
</Grid.Col> </Grid.Col>
)} )}
</Grid> </Grid>
{withBottomLine && <Divider marginTop={0} />} {withBottomLine && <Divider color={AccentColor.blue} marginTop={0} />}
</> </>
); );
}; };

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 }]}

20
constants/api-storage.ts Normal file
View File

@@ -0,0 +1,20 @@
const API_IMAGE = {
/**
*
* @param fileId | file id from wibu storage , atau bisa disimpan di DB
* @param size | file size 10 - 1000 , tergantung ukuran file dan kebutuhan saar di tampilkan
* @type {string}
*/
GET: ({ fileId, size }: { fileId: string; size?: string }) =>
size
? `https://wibu-storage.wibudev.com/api/files/${fileId}-size-${size}`
: `https://wibu-storage.wibudev.com/api/files/${fileId}`,
/**
* @type {string}
* @returns alamat API dari wibu storage
*/
GET_NO_PARAMS: "https://wibu-storage.wibudev.com/api/files/",
};
export default API_IMAGE;

View File

@@ -1,71 +1,73 @@
export {listDataNotPublishInvesment, listDataPublishInvesment}; import { formatCurrencyDisplay } from "@/utils/formatCurrencyDisplay";
const listDataNotPublishInvesment = [ export { listDataNotPublishInvesment, listDataPublishInvesment };
const listDataNotPublishInvesment = ({ data }: { data: any }) => [
{ {
label: "Target Dana", label: "Target Dana",
value: "Rp. 7.500.000", value: `Rp. ${formatCurrencyDisplay(data?.targetDana) || "-"}`,
}, },
{ {
label: "Harga Per Lembar", label: "Harga Per Lembar",
value: "Rp. 2.400", value: `Rp. ${formatCurrencyDisplay(data?.hargaLembar) || "-"}`,
}, },
{ {
label: "Return Of Investment (ROI)", label: "Return Of Investment (ROI)",
value: "3 %", value: `${data?.roi || "-"} %`,
}, },
{ {
label: "Total Lembar", label: "Total Lembar",
value: "1.200", value: data?.totalLembar || "-",
},
{
label: "Jadwal Pembagian",
value: "Rp. 2.880.000",
},
{
label: "Pembagian Deviden",
value: "Selamanya",
}, },
{ {
label: "Pencarian Investor", label: "Pencarian Investor",
value: "30 Hari", value: data && data?.MasterPencarianInvestor?.name + " hari" || "-",
},
{
label: "Jadwal Pembagian",
value: data && data?.MasterPembagianDeviden?.name + " bulan" || "-",
},
{
label: "Pembagian Deviden",
value: data?.MasterPeriodeDeviden?.name || "-",
}, },
]; ];
const listDataPublishInvesment = [ const listDataPublishInvesment = ({ data }: { data: any }) => [
{ {
label: "Investor", label: "Investor",
value: "10", value: data?.investor,
}, },
{ {
label: "Target Dana", label: "Target Dana",
value: "Rp. 7.500.000", value: data?.targetDana,
}, },
{ {
label: "Harga Per Lembar", label: "Harga Per Lembar",
value: "Rp. 2.400", value: data?.hargaPerLembar,
}, },
{ {
label: "Return Of Investment (ROI)", label: "Return Of Investment (ROI)",
value: "3 %", value: data?.roi + " %",
}, },
{ {
label: "Total Lembar", label: "Total Lembar",
value: "1.200", value: data?.totalLembar,
}, },
{ {
label: "Sisa Lembar", label: "Sisa Lembar",
value: "600", value: data?.sisaLembar,
}, },
{ {
label: "Jadwal Pembagian", label: "Jadwal Pembagian",
value: "Rp. 2.880.000", value: data?.jadwalPembagian,
}, },
{ {
label: "Pembagian Deviden", label: "Pembagian Deviden",
value: "Selamanya", value: data?.pembagianDeviden,
}, },
{ {
label: "Pencarian Investor", label: "Pencarian Investor",
value: "30 Hari", value: data?.pencarianInvestor,
}, },
]; ];

18107
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -11,65 +11,70 @@
"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-keyboard-controller": "^1.18.6",
"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

@@ -1,22 +1,33 @@
import { import {
AvatarUsernameAndOtherComponent, AvatarUsernameAndOtherComponent,
BoxWithHeaderSection, BoxWithHeaderSection,
Grid, Grid,
StackCustom, Spacing,
TextCustom StackCustom,
TextCustom,
} from "@/components"; } from "@/components";
export default function Collaboration_BoxDetailSection({ id }: { id: string }) { export default function Collaboration_BoxDetailSection({
data,
}: {
data: any;
}) {
return ( return (
<> <>
<BoxWithHeaderSection> <BoxWithHeaderSection>
<AvatarUsernameAndOtherComponent
avatar={data?.Author?.Profile?.imageId}
name={data?.Author?.username}
avatarHref={`/profile/${data?.Author?.Profile?.id}`}
withBottomLine
/>
<Spacing height={10}/>
<StackCustom> <StackCustom>
<AvatarUsernameAndOtherComponent />
<TextCustom align="center" bold size="large"> <TextCustom align="center" bold size="large">
Judul Proyek {id} {data?.title || ""}
</TextCustom> </TextCustom>
{listData.map((item, index) => ( {listData(data).map((item, index) => (
<Grid key={index}> <Grid key={index}>
<Grid.Col span={4}> <Grid.Col span={4}>
<TextCustom bold>{item.title}</TextCustom> <TextCustom bold>{item.title}</TextCustom>
@@ -32,23 +43,21 @@ export default function Collaboration_BoxDetailSection({ id }: { id: string }) {
); );
} }
const listData = [ const listData = (data: any) => [
{ {
title: "Industri", title: "Industri",
value: "Pilihan Industri", value: data?.ProjectCollaborationMaster_Industri?.name || "-",
}, },
{ {
title: "Deskripsi", title: "Lokasi",
value: "Deskripsi Proyek", value: data?.lokasi || "-",
}, },
{ {
title: "Tujuan Proyek", title: "Tujuan Proyek",
value: value: data?.purpose || "-",
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
}, },
{ {
title: "Keuntungan Proyek", title: "Keuntungan Proyek",
value: value: data?.benefit || "-",
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
}, },
]; ];

View File

@@ -7,24 +7,12 @@ import {
import { Href } from "expo-router"; import { Href } from "expo-router";
function Collaboration_BoxPublishSection({ function Collaboration_BoxPublishSection({
id,
title,
username,
description,
href, href,
data,
// Avatar
sourceAvatar,
rightComponentAvatar, rightComponentAvatar,
}: { }: {
id: string;
title?: string;
username?: string;
description?: string;
href: Href; href: Href;
data: any;
// Avatar
sourceAvatar?: string;
rightComponentAvatar?: React.ReactNode; rightComponentAvatar?: React.ReactNode;
}) { }) {
return ( return (
@@ -32,21 +20,18 @@ function Collaboration_BoxPublishSection({
<BoxWithHeaderSection href={href}> <BoxWithHeaderSection href={href}>
<StackCustom gap={0}> <StackCustom gap={0}>
<AvatarUsernameAndOtherComponent <AvatarUsernameAndOtherComponent
avatarHref={`/profile/${id}`} avatarHref={`/profile/${data?.Author?.id}`}
name={username || "Username"} name={data?.Author?.username || "Username"}
rightComponent={rightComponentAvatar} rightComponent={rightComponentAvatar}
avatar={sourceAvatar as any} avatar={data?.Author?.Profile?.imageId}
withBottomLine withBottomLine
/> />
<StackCustom> <StackCustom>
<TextCustom truncate={2} size="large" bold align="center"> <TextCustom truncate size="large" bold align="center">
{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?.purpose || "-"}</TextCustom>
{/* <TextCustom bold size="small" > {/* <TextCustom bold size="small" >
2 Partisipan 2 Partisipan
</TextCustom> */} </TextCustom> */}

View File

@@ -0,0 +1,222 @@
// ChatScreen.tsx
import React, { useEffect, useRef, useState } from "react";
import {
Dimensions,
Keyboard,
Platform,
ScrollView,
StyleSheet,
Text,
TextInput,
TouchableOpacity,
View,
} from "react-native";
import {
SafeAreaView
} from "react-native-safe-area-context";
type Message = {
id: string;
text: string;
sender: "me" | "other";
timestamp: Date;
};
const { height: SCREEN_HEIGHT } = Dimensions.get("window");
const ChatScreen: React.FC = () => {
const [messages, setMessages] = useState<Message[]>([
{
id: "1",
text: "Hai!",
sender: "other",
timestamp: new Date(Date.now() - 300000),
},
{
id: "2",
text: "Halo juga!",
sender: "me",
timestamp: new Date(Date.now() - 240000),
},
{
id: "3",
text: "Apa kabar?",
sender: "other",
timestamp: new Date(Date.now() - 180000),
},
]);
const [inputText, setInputText] = useState<string>("");
const [keyboardHeight, setKeyboardHeight] = useState<number>(0);
const scrollViewRef = useRef<ScrollView>(null);
useEffect(() => {
const show = Keyboard.addListener("keyboardDidShow", (e) => {
let kbHeight = e.endCoordinates.height;
// Di Android dengan edge-to-edge, kadang tinggi termasuk navigation bar
if (Platform.OS === "android") {
// Batasi maksimal 60% layar
kbHeight = Math.min(kbHeight, SCREEN_HEIGHT * 2);
}
setKeyboardHeight(kbHeight);
});
const hide = Keyboard.addListener("keyboardDidHide", () => {
setKeyboardHeight(0);
});
return () => {
show.remove();
hide.remove();
};
}, []);
useEffect(() => {
// Scroll ke bawah setelah pesan baru atau keyboard muncul
const timer = setTimeout(() => {
scrollViewRef.current?.scrollToEnd({ animated: true });
}, 100); // delay kecil untuk pastikan layout stabil
return () => clearTimeout(timer);
}, [messages, keyboardHeight]);
const handleSend = () => {
if (!inputText.trim()) return;
setMessages((prev) => [
...prev,
{
id: Date.now().toString(),
text: inputText.trim(),
sender: "me",
timestamp: new Date(),
},
]);
setInputText("");
};
return (
<SafeAreaView style={styles.safeArea}>
{/* Kontainer utama dengan padding bottom = tinggi keyboard */}
<View style={[styles.container, { paddingBottom: keyboardHeight }]}>
<ScrollView
ref={scrollViewRef}
style={styles.messagesContainer}
contentContainerStyle={styles.messagesContent}
keyboardShouldPersistTaps="handled"
>
{messages.map((msg) => (
<View
key={msg.id}
style={[
styles.messageBubble,
msg.sender === "me" ? styles.myMessage : styles.otherMessage,
]}
>
<Text style={styles.messageText}>{msg.text}</Text>
<Text style={styles.timestamp}>
{msg.timestamp.toLocaleTimeString([], {
hour: "2-digit",
minute: "2-digit",
})}
</Text>
</View>
))}
</ScrollView>
<View style={[styles.inputContainer, { paddingBottom: 10 }]}>
<TextInput
value={inputText}
onChangeText={setInputText}
placeholder="Ketik pesan..."
style={styles.textInput}
multiline
blurOnSubmit={false}
scrollEnabled={Platform.OS === "ios"} // ✅ Aktifkan scroll hanya di iOS
textAlignVertical="top"
/>
<TouchableOpacity
style={styles.sendButton}
onPress={handleSend}
disabled={!inputText.trim()}
>
<Text style={styles.sendButtonText}>Kirim</Text>
</TouchableOpacity>
</View>
</View>
</SafeAreaView>
);
};
const styles = StyleSheet.create({
safeArea: {
flex: 1,
backgroundColor: "#e5ddd5",
},
container: {
flex: 1,
backgroundColor: "#e5ddd5",
},
messagesContainer: {
flex: 1,
paddingHorizontal: 10,
},
messagesContent: {
paddingBottom: 10,
},
messageBubble: {
maxWidth: "80%",
padding: 10,
marginVertical: 4,
borderRadius: 12,
},
myMessage: {
alignSelf: "flex-end",
backgroundColor: "#dcf8c6",
},
otherMessage: {
alignSelf: "flex-start",
backgroundColor: "#ffffff",
},
messageText: {
fontSize: 16,
color: "#000",
},
timestamp: {
fontSize: 10,
color: "#666",
textAlign: "right",
marginTop: 4,
},
inputContainer: {
flexDirection: "row",
alignItems: "flex-end",
paddingHorizontal: 10,
paddingTop: 8,
backgroundColor: "#f0f0f0",
borderTopWidth: 1,
borderTopColor: "#ddd",
},
textInput: {
flex: 1,
borderWidth: 1,
borderColor: "#ccc",
borderRadius: 20,
paddingHorizontal: 12,
paddingVertical: 8,
fontSize: 16,
backgroundColor: "#fff",
maxHeight: 100,
},
sendButton: {
marginLeft: 10,
backgroundColor: "#34b7f1",
paddingHorizontal: 16,
paddingVertical: 10,
borderRadius: 20,
},
sendButtonText: {
color: "#fff",
fontWeight: "bold",
},
});
export default ChatScreen;

View File

@@ -0,0 +1,292 @@
/* eslint-disable react-hooks/exhaustive-deps */
// ChatScreen.tsx
import { LoaderCustom } from "@/components";
import { AccentColor } from "@/constants/color-palet";
import { ICON_SIZE_SMALL } from "@/constants/constans-value";
import { useAuth } from "@/hooks/use-auth";
import {
apiCollaborationGroupMessage,
apiCollaborationGroupMessageCreate,
} from "@/service/api-client/api-collaboration";
import { formatChatTime } from "@/utils/formatChatTime";
import { AntDesign } from "@expo/vector-icons";
import dayjs from "dayjs";
import { useFocusEffect } from "expo-router";
import _ from "lodash";
import React, { useCallback, useEffect, useRef, useState } from "react";
import {
Dimensions,
Keyboard,
Platform,
ScrollView,
StyleSheet,
Text,
TextInput,
TouchableOpacity,
View,
} from "react-native";
import { ActivityIndicator } from "react-native-paper";
import { SafeAreaView } from "react-native-safe-area-context";
type IMessage = {
id: string;
createdAt: Date;
isActive: boolean;
message: string;
isFile: boolean;
userId: string;
User: {
select: {
id: true;
username: true;
};
};
};
const { height: SCREEN_HEIGHT } = Dimensions.get("window");
const ChatScreen: React.FC<{ id: string }> = ({ id }) => {
const { user } = useAuth();
const [messages, setMessages] = useState<IMessage[] | null>(null);
const [loadingMessage, setLoadingMessage] = useState<boolean>(false);
const [inputText, setInputText] = useState<string>("");
const [keyboardHeight, setKeyboardHeight] = useState<number>(0);
const scrollViewRef = useRef<ScrollView>(null);
useFocusEffect(
useCallback(() => {
onLoadMessage();
}, [id])
);
const onLoadMessage = async () => {
try {
setLoadingMessage(true);
const response = await apiCollaborationGroupMessage({ id: id as string });
if (response.success) {
setMessages(response.data);
}
} catch (error) {
console.log("[ERROR]", error);
} finally {
setLoadingMessage(false);
}
};
useEffect(() => {
const show = Keyboard.addListener("keyboardDidShow", (e) => {
let kbHeight = e.endCoordinates.height;
// Di Android dengan edge-to-edge, kadang tinggi termasuk navigation bar
if (Platform.OS === "android") {
// Batasi maksimal 60% layar
kbHeight = Math.min(kbHeight, SCREEN_HEIGHT * 0.6);
}
setKeyboardHeight(kbHeight);
});
const hide = Keyboard.addListener("keyboardDidHide", () => {
setKeyboardHeight(0);
});
return () => {
show.remove();
hide.remove();
};
}, []);
useEffect(() => {
// Scroll ke bawah setelah pesan baru atau keyboard muncul
const timer = setTimeout(() => {
scrollViewRef.current?.scrollToEnd({ animated: true });
}, 100); // delay kecil untuk pastikan layout stabil
return () => clearTimeout(timer);
}, [messages, keyboardHeight]);
const handleSend = async () => {
if (!inputText.trim()) return;
const newData = {
userId: user?.id,
message: inputText.trim(),
};
try {
const response = await apiCollaborationGroupMessageCreate({
id: id as string,
data: newData,
});
if (response.success) {
setMessages((prev: IMessage | any) => [
...prev,
{
id: Date.now().toString(),
createdAt: new Date(),
isActive: true,
message: inputText.trim(),
isFile: false,
userId: user?.id,
User: {
username: user?.username,
},
},
]);
setInputText("");
}
} catch (error) {
console.log("[ERROR]", error);
}
};
return (
<SafeAreaView style={styles.safeArea} edges={["bottom"]}>
{/* Kontainer utama dengan padding bottom = tinggi keyboard */}
<View style={[styles.container, { paddingBottom: keyboardHeight }]}>
<ScrollView
ref={scrollViewRef}
style={styles.messagesContainer}
contentContainerStyle={styles.messagesContent}
keyboardShouldPersistTaps="handled"
>
{loadingMessage ? (
<ActivityIndicator color="black" size={"large"} />
) : _.isEmpty(messages) ? (
<Text style={styles.isEmptyMessage}>
Belum ada pesan
</Text>
) : (
messages?.map((item: any) => (
<View
key={item.id}
style={[
styles.messageBubble,
item.userId === user?.id
? styles.myMessage
: styles.otherMessage,
]}
>
<Text style={styles.name}>{item?.User?.username}</Text>
<Text style={styles.messageText}>{item.message}</Text>
<Text style={styles.timestamp}>
{formatChatTime(item.createdAt)}
</Text>
</View>
))
)}
</ScrollView>
<View style={[styles.inputContainer, { paddingBottom: 10 }]}>
<TextInput
value={inputText}
onChangeText={setInputText}
placeholder="Ketik pesan..."
style={styles.textInput}
multiline
blurOnSubmit={false}
scrollEnabled={Platform.OS === "ios"} // ✅ Aktifkan scroll hanya di iOS
textAlignVertical="top"
/>
<TouchableOpacity
activeOpacity={0.7}
style={styles.sendButton}
onPress={handleSend}
disabled={!inputText.trim()}
>
<AntDesign name="send" size={ICON_SIZE_SMALL} color="white"/>
{/* <Text style={styles.sendButtonText}>Kirim</Text> */}
</TouchableOpacity>
</View>
</View>
</SafeAreaView>
);
};
const styles = StyleSheet.create({
safeArea: {
flex: 1,
backgroundColor: "#e5ddd5",
},
container: {
flex: 1,
backgroundColor: "#e5ddd5",
},
isEmptyMessage: {
alignSelf: "center",
color: "#666",
marginTop: 20,
},
messagesContainer: {
flex: 1,
paddingHorizontal: 10,
},
messagesContent: {
paddingBottom: 10,
},
messageBubble: {
maxWidth: "80%",
padding: 10,
marginVertical: 4,
borderRadius: 12,
},
name: {
fontSize: 12,
color: "#666",
marginBottom: 4,
},
myMessage: {
alignSelf: "flex-end",
backgroundColor: "#dcf8c6",
},
otherMessage: {
alignSelf: "flex-start",
backgroundColor: "#ffffff",
},
messageText: {
fontSize: 16,
color: "#000",
},
timestamp: {
fontSize: 10,
color: "#666",
textAlign: "right",
marginTop: 4,
},
inputContainer: {
flexDirection: "row",
alignItems: "flex-end",
paddingHorizontal: 10,
paddingTop: 8,
backgroundColor: "#f0f0f0",
borderTopWidth: 1,
borderTopColor: "#ddd",
},
textInput: {
flex: 1,
borderWidth: 1,
borderColor: "#ccc",
borderRadius: 20,
paddingHorizontal: 12,
paddingVertical: 8,
fontSize: 16,
backgroundColor: "#fff",
maxHeight: 100,
},
sendButton: {
marginLeft: 10,
backgroundColor: AccentColor.blue,
// paddingHorizontal: 16,
// paddingVertical: 10,
borderRadius: "100%",
width: 40,
height: 40,
justifyContent: "center",
alignContent: "center",
alignItems: "center",
},
sendButtonText: {
color: "#fff",
fontWeight: "bold",
},
});
export default ChatScreen;

View File

@@ -15,10 +15,12 @@ export default function Collaboration_ProjectMainSelectedSection({
selected, selected,
setSelected, setSelected,
setOpenDrawerParticipant, setOpenDrawerParticipant,
listData,
}: { }: {
selected: (string | number)[]; selected: (string | number)[];
setSelected: (value: (string | number)[]) => void; setSelected: (value: (string | number)[]) => void;
setOpenDrawerParticipant: (value: boolean) => void; setOpenDrawerParticipant: (value: boolean) => void;
listData: any[];
}) { }) {
return ( return (
<BaseBox style={{ height: 500 }}> <BaseBox style={{ height: 500 }}>
@@ -31,7 +33,7 @@ export default function Collaboration_ProjectMainSelectedSection({
</TextCustom> </TextCustom>
<CheckboxGroup value={selected} onChange={setSelected}> <CheckboxGroup value={selected} onChange={setSelected}>
{Array.from({ length: 5 }).map((_, index) => ( {listData?.map((item: any, index: any) => (
<View key={index}> <View key={index}>
<Grid key={index}> <Grid key={index}>
<Grid.Col <Grid.Col

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,28 +1,158 @@
import { ButtonCustom, Grid } from "@/components"; import { AlertDefaultSystem, ButtonCustom, Grid } from "@/components";
import { View } from "react-native"; import {
apiEventDelete,
apiEventUpdateStatus,
} from "@/service/api-client/api-event";
import { router } from "expo-router";
import Toast from "react-native-toast-message";
export default function Event_ButtonStatusSection({ export default function Event_ButtonStatusSection({
id,
status, status,
onOpenAlert,
onOpenDeleteAlert,
}: { }: {
id: string;
status: string; status: string;
onOpenAlert: (value: boolean) => void;
onOpenDeleteAlert: (value: boolean) => void;
}) { }) {
const handleBatalkanReview = () => {
AlertDefaultSystem({
title: "Batalkan Review",
message: "Apakah Anda yakin ingin batalkan review ini?",
textLeft: "Batal",
textRight: "Ya",
onPressRight: async () => {
try {
const response = await apiEventUpdateStatus({
id: id,
status: "draft",
});
const handleOpenAlert = () => { if (response.success) {
onOpenAlert(true); 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);
}
},
});
};
const handleAjukanReview = () => {
AlertDefaultSystem({
title: "Ajukan Review",
message: "Apakah Anda yakin ingin ajukan review ini?",
textLeft: "Batal",
textRight: "Ya",
onPressRight: async () => {
try {
const response = await apiEventUpdateStatus({
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);
}
},
});
};
const handleEditKembali = () => {
AlertDefaultSystem({
title: "Edit Kembali",
message: "Apakah Anda yakin ingin edit kembali ini?",
textLeft: "Batal",
textRight: "Ya",
onPressRight: async () => {
try {
const response = await apiEventUpdateStatus({
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);
}
},
});
}; };
const handleOpenDeleteAlert = () => { const handleOpenDeleteAlert = () => {
onOpenDeleteAlert(true); AlertDefaultSystem({
title: "Hapus",
message: "Apakah Anda yakin ingin menghapus data ini?",
textLeft: "Batal",
textRight: "Hapus",
onPressRight: async () => {
try {
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);
}
},
});
}; };
const DeleteButton = () => { const DeleteButton = () => {
return ( return (
<> <>
<ButtonCustom backgroundColor="red" textColor="white" onPress={handleOpenDeleteAlert}> <ButtonCustom
backgroundColor="red"
textColor="white"
onPress={handleOpenDeleteAlert}
>
Hapus Hapus
</ButtonCustom> </ButtonCustom>
</> </>
@@ -35,7 +165,7 @@ export default function Event_ButtonStatusSection({
case "review": case "review":
return ( return (
<ButtonCustom onPress={handleOpenAlert}> <ButtonCustom onPress={handleBatalkanReview}>
Batalkan Review Batalkan Review
</ButtonCustom> </ButtonCustom>
); );
@@ -44,13 +174,14 @@ export default function Event_ButtonStatusSection({
return ( return (
<> <>
<Grid> <Grid>
<Grid.Col span={5}> <Grid.Col span={6} style={{ paddingRight: 10 }}>
<ButtonCustom onPress={handleOpenAlert}>Ajukan Review</ButtonCustom> <ButtonCustom onPress={handleAjukanReview}>
Ajukan Review
</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>
</> </>
); );
@@ -59,13 +190,14 @@ export default function Event_ButtonStatusSection({
return ( return (
<> <>
<Grid> <Grid>
<Grid.Col span={5}> <Grid.Col span={6} style={{ paddingRight: 10 }}>
<ButtonCustom onPress={handleOpenAlert}>Edit Kembali</ButtonCustom> <ButtonCustom onPress={handleEditKembali}>
Edit Kembali
</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,30 +1,44 @@
import { import {
AvatarCustom, AvatarComp,
BaseBox, BoxWithHeaderSection,
ClickableCustom, ClickableCustom,
Grid, Grid,
Spacing, Spacing,
TextCustom, TextCustom
} from "@/components"; } 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 { GStyles } from "@/styles/global-styles";
import { formatChatTime } from "@/utils/formatChatTime";
import { Entypo } from "@expo/vector-icons"; import { Entypo } from "@expo/vector-icons";
import { View } from "react-native"; import { View } from "react-native";
export default function Forum_CommentarBoxSection({ export default function Forum_CommentarBoxSection({
data, data,
setOpenDrawer, onSetData,
}: { }: {
data: any; data: any;
setOpenDrawer: (value: boolean) => void; onSetData: ({
setCommentId,
setOpenDrawer,
setCommentAuthorId,
}: {
setCommentId: string;
setOpenDrawer: boolean;
setCommentAuthorId: string;
}) => void;
}) { }) {
return ( return (
<> <>
<BaseBox> <BoxWithHeaderSection>
<View> <View>
<Grid> <Grid>
<Grid.Col span={2}> <Grid.Col span={2}>
<AvatarCustom href={`/profile/${data.id}`} /> <AvatarComp
href={`/profile/${data?.Author?.Profile?.id}`}
fileId={data?.Author?.Profile?.imageId}
size="base"
/>
</Grid.Col> </Grid.Col>
<Grid.Col <Grid.Col
span={8} span={8}
@@ -32,7 +46,7 @@ export default function Forum_CommentarBoxSection({
justifyContent: "center", justifyContent: "center",
}} }}
> >
<TextCustom>{data.name}</TextCustom> <TextCustom>{data?.Author?.username}</TextCustom>
</Grid.Col> </Grid.Col>
<Grid.Col <Grid.Col
@@ -43,7 +57,11 @@ export default function Forum_CommentarBoxSection({
> >
<ClickableCustom <ClickableCustom
onPress={() => { onPress={() => {
setOpenDrawer(true); onSetData({
setCommentId: data?.id,
setOpenDrawer: true,
setCommentAuthorId: data?.Author?.id,
});
}} }}
style={{ style={{
alignItems: "flex-end", alignItems: "flex-end",
@@ -58,14 +76,18 @@ export default function Forum_CommentarBoxSection({
</Grid.Col> </Grid.Col>
</Grid> </Grid>
<TextCustom>{data.deskripsi}</TextCustom> <View style={GStyles.forumBox}>
<TextCustom>{data.komentar}</TextCustom>
</View>
<Spacing /> <Spacing height={10} />
<View style={{ alignItems: "flex-end" }}> <View style={{ alignItems: "flex-end" }}>
<TextCustom>{data.date}</TextCustom> <TextCustom size="small" color="gray">
{formatChatTime(data?.createdAt)}
</TextCustom>
</View> </View>
</View> </View>
</BaseBox> </BoxWithHeaderSection>
</> </>
); );
} }

View File

@@ -1,5 +1,5 @@
import { import {
AvatarCustom, AvatarComp,
BaseBox, BaseBox,
ClickableCustom, ClickableCustom,
Grid, Grid,
@@ -8,6 +8,8 @@ import {
} from "@/components"; } 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 { GStyles } from "@/styles/global-styles";
import { formatChatTime } from "@/utils/formatChatTime";
import { Entypo, Ionicons } from "@expo/vector-icons"; import { Entypo, Ionicons } from "@expo/vector-icons";
import { Href, router } from "expo-router"; import { Href, router } from "expo-router";
import { View } from "react-native"; import { View } from "react-native";
@@ -15,28 +17,32 @@ import { View } from "react-native";
export default function Forum_BoxDetailSection({ export default function Forum_BoxDetailSection({
data, data,
isTruncate, isTruncate,
setOpenDrawer,
setStatus,
href, href,
isRightComponent = true,
onSetData,
}: { }: {
data: any; data: any;
isTruncate?: boolean; isTruncate?: boolean;
setOpenDrawer: (value: boolean) => void;
setStatus: (value: string) => void;
href?: Href; href?: Href;
isRightComponent?: boolean;
onSetData: ({
setDataId,
setStatus,
setOpenDrawer,
setAuthorId,
}: {
setDataId: string;
setStatus: string;
setOpenDrawer: boolean;
setAuthorId: string;
}) => void;
}) { }) {
const deskripsiView = ( const deskripsiView = (
<View <View style={GStyles.forumBox}>
style={{
backgroundColor: MainColor.soft_darkblue,
padding: 8,
borderRadius: 8,
}}
>
{isTruncate ? ( {isTruncate ? (
<TextCustom truncate={2}>{data.deskripsi}</TextCustom> <TextCustom truncate={2}>{data?.diskusi}</TextCustom>
) : ( ) : (
<TextCustom>{data.deskripsi}</TextCustom> <TextCustom>{data?.diskusi}</TextCustom>
)} )}
</View> </View>
); );
@@ -47,42 +53,53 @@ export default function Forum_BoxDetailSection({
<View> <View>
<Grid> <Grid>
<Grid.Col span={2}> <Grid.Col span={2}>
<AvatarCustom href={`/profile/${data.id}`} /> <AvatarComp
fileId={data?.Author?.Profile?.imageId}
href={`/forum/${data?.Author?.id}/forumku`}
size={"base"}
/>
</Grid.Col> </Grid.Col>
<Grid.Col span={8}> <Grid.Col span={6}>
<TextCustom>{data.name}</TextCustom> <TextCustom truncate>{data?.Author?.username}</TextCustom>
{data.status === "Open" ? ( {data?.ForumMaster_StatusPosting?.status === "Open" ? (
<TextCustom bold size="small" color="green"> <TextCustom bold size="small" color="green">
{data.status} {data?.ForumMaster_StatusPosting?.status}
</TextCustom> </TextCustom>
) : ( ) : (
<TextCustom bold size="small" color="red"> <TextCustom bold size="small" color="red">
{data.status} {data?.ForumMaster_StatusPosting?.status}
</TextCustom> </TextCustom>
)} )}
</Grid.Col> </Grid.Col>
<Grid.Col <Grid.Col
span={2} span={4}
style={{ style={{
justifyContent: "center", justifyContent: "flex-start",
alignItems: "flex-end",
}} }}
> >
<ClickableCustom {isRightComponent && (
onPress={() => { <ClickableCustom
setOpenDrawer(true); onPress={() => {
setStatus(data.status); onSetData({
}} setDataId: data?.id,
style={{ setStatus: data?.ForumMaster_StatusPosting?.status,
alignItems: "flex-end", setAuthorId: data?.Author?.id,
}} setOpenDrawer: true,
> });
<Entypo }}
name="dots-three-horizontal" style={{
color={MainColor.white} alignItems: "flex-end",
size={ICON_SIZE_SMALL} }}
/> >
</ClickableCustom> <Entypo
name="dots-three-horizontal"
color={MainColor.white}
size={ICON_SIZE_SMALL}
/>
</ClickableCustom>
)}
</Grid.Col> </Grid.Col>
</Grid> </Grid>
@@ -110,11 +127,13 @@ export default function Forum_BoxDetailSection({
size={ICON_SIZE_SMALL} size={ICON_SIZE_SMALL}
color={MainColor.white} color={MainColor.white}
/> />
<TextCustom>{data.jumlahBalas}</TextCustom> <TextCustom>{data?.count}</TextCustom>
</View> </View>
</Grid.Col> </Grid.Col>
<Grid.Col span={6} style={{ alignItems: "flex-end" }}> <Grid.Col span={6} style={{ alignItems: "flex-end" }}>
<TextCustom size="small"> {data.date}</TextCustom> <TextCustom truncate size="small" color="gray">
{formatChatTime(data?.createdAt)}
</TextCustom>
</Grid.Col> </Grid.Col>
</Grid> </Grid>
</View> </View>

View File

@@ -2,24 +2,20 @@ import { MainColor } from "@/constants/color-palet";
import { ICON_SIZE_SMALL } from "@/constants/constans-value"; import { ICON_SIZE_SMALL } from "@/constants/constans-value";
import { Feather, Ionicons } from "@expo/vector-icons"; import { Feather, Ionicons } from "@expo/vector-icons";
export { drawerItemsForumBeranda, drawerItemsForumComentar }; export {
drawerItemsForumBerandaForAuthor,
drawerItemsForumComentarForAuthor,
drawerItemsForumBerandaForNonAuthor,
drawerItemsForumComentarForNonAuthor,
};
const drawerItemsForumBeranda = ({ const drawerItemsForumBerandaForAuthor = ({
id, id,
status, status,
}: { }: {
id: string; id: string;
status: string; status: string;
}) => [ }) => [
{
icon: (
<Ionicons name="flag" size={ICON_SIZE_SMALL} color={MainColor.white} />
),
label: "Laporkan diskusi",
// color: MainColor.white,
path: `/forum/${id}/report-posting`,
},
{ {
icon: ( icon: (
<Feather name="edit" size={ICON_SIZE_SMALL} color={MainColor.white} /> <Feather name="edit" size={ICON_SIZE_SMALL} color={MainColor.white} />
@@ -49,15 +45,18 @@ const drawerItemsForumBeranda = ({
}, },
]; ];
const drawerItemsForumComentar = ({ id }: { id: string }) => [ const drawerItemsForumBerandaForNonAuthor = ({ id }: { id: string }) => [
{ {
icon: ( icon: (
<Ionicons name="flag" size={ICON_SIZE_SMALL} color={MainColor.white} /> <Ionicons name="flag" size={ICON_SIZE_SMALL} color={MainColor.white} />
), ),
label: "Laporkan", label: "Laporkan diskusi",
// color: MainColor.white, // color: MainColor.white,
path: `/forum/${id}/report-commentar`, path: `/forum/${id}/report-posting`,
}, },
];
const drawerItemsForumComentarForAuthor = ({ id }: { id: string }) => [
{ {
icon: ( icon: (
<Ionicons name="trash" size={ICON_SIZE_SMALL} color={MainColor.white} /> <Ionicons name="trash" size={ICON_SIZE_SMALL} color={MainColor.white} />
@@ -67,3 +66,14 @@ const drawerItemsForumComentar = ({ id }: { id: string }) => [
path: "", path: "",
}, },
]; ];
const drawerItemsForumComentarForNonAuthor = ({ id }: { id: string }) => [
{
icon: (
<Ionicons name="flag" size={ICON_SIZE_SMALL} color={MainColor.white} />
),
label: "Laporkan",
// color: MainColor.white,
path: `/forum/${id}/report-commentar`,
},
];

View File

@@ -1,26 +1,67 @@
import { IMenuDrawerItem } from "@/components/_Interface/types"; import { IMenuDrawerItem } from "@/components/_Interface/types";
import MenuDrawerDynamicGrid from "@/components/Drawer/MenuDrawerDynamicGird"; import MenuDrawerDynamicGrid from "@/components/Drawer/MenuDrawerDynamicGird";
import { router } from "expo-router"; import { router } from "expo-router";
import { drawerItemsForumBeranda } from "../ListPage"; import { AlertDefaultSystem } from "@/components";
import {
drawerItemsForumBerandaForAuthor,
drawerItemsForumBerandaForNonAuthor,
} from "../ListPage";
import { useAuth } from "@/hooks/use-auth";
import { apiForumDelete } from "@/service/api-client/api-forum";
import Toast from "react-native-toast-message";
export default function Forum_MenuDrawerBerandaSection({ export default function Forum_MenuDrawerBerandaSection({
id, id,
status, status,
setIsDrawerOpen, setIsDrawerOpen,
setShowDeleteAlert, authorId,
setShowAlertStatus, handlerUpdateStatus,
}: { }: {
id: string; id: string;
status: string; status: string;
setIsDrawerOpen: (value: boolean) => void; setIsDrawerOpen: (value: boolean) => void;
setShowDeleteAlert: (value: boolean) => void; authorId: string;
setShowAlertStatus: (value: boolean) => void; handlerUpdateStatus?: (value: string) => void;
}) { }) {
const { user } = useAuth();
const handlePress = (item: IMenuDrawerItem) => { const handlePress = (item: IMenuDrawerItem) => {
if (item.label === "Hapus") { if (item.label === "Hapus") {
setShowDeleteAlert(true); AlertDefaultSystem({
title: "Hapus diskusi",
message: "Apakah Anda yakin ingin menghapus diskusi ini?",
textLeft: "Batal",
textRight: "Hapus",
onPressRight: async () => {
try {
const response = await apiForumDelete({ id });
if (response.success) {
Toast.show({
type: "success",
text1: "Berhasil dihapus",
});
router.back();
} else {
Toast.show({
type: "error",
text1: response.message,
});
}
} catch (error) {
console.log("[ERROR]", error);
}
},
});
} else if (item.label === "Buka forum" || item.label === "Tutup forum") { } else if (item.label === "Buka forum" || item.label === "Tutup forum") {
setShowAlertStatus(true); AlertDefaultSystem({
title: "Ubah Status",
message: "Apakah Anda yakin ingin mengubah status forum ini?",
textLeft: "Batal",
textRight: "Ubah",
onPressRight: () => {
handlerUpdateStatus?.(item.label === "Buka forum" ? "Open" : "Closed");
},
});
} else { } else {
router.push(item.path as any); router.push(item.path as any);
} }
@@ -32,9 +73,13 @@ export default function Forum_MenuDrawerBerandaSection({
<> <>
{/* Menu Items */} {/* Menu Items */}
<MenuDrawerDynamicGrid <MenuDrawerDynamicGrid
data={drawerItemsForumBeranda({ id, status })} data={
authorId === user?.id
? drawerItemsForumBerandaForAuthor({ id, status })
: drawerItemsForumBerandaForNonAuthor({ id })
}
columns={4} // Ubah ke 2 jika ingin 2 kolom per baris columns={4} // Ubah ke 2 jika ingin 2 kolom per baris
onPressItem={handlePress} onPressItem={handlePress as any}
/> />
</> </>
); );

View File

@@ -1,19 +1,67 @@
import { MenuDrawerDynamicGrid } from "@/components"; import { AlertDefaultSystem, MenuDrawerDynamicGrid } from "@/components";
import { drawerItemsForumComentar } from "../ListPage"; import { useAuth } from "@/hooks/use-auth";
import { router } from "expo-router"; import { router } from "expo-router";
import {
drawerItemsForumComentarForAuthor,
drawerItemsForumComentarForNonAuthor,
} from "../ListPage";
import { apiForumDeleteComment } from "@/service/api-client/api-forum";
import Toast from "react-native-toast-message";
export default function Forum_MenuDrawerCommentar({ export default function Forum_MenuDrawerCommentar({
id, id,
setShowDeleteAlert,
setIsDrawerOpen, setIsDrawerOpen,
commentId,
commentAuthorId,
listComment,
setListComment,
countComment,
setCountComment,
}: { }: {
id: string; id: string;
setShowDeleteAlert: (value: boolean) => void;
setIsDrawerOpen: (value: boolean) => void; setIsDrawerOpen: (value: boolean) => void;
commentId: string;
commentAuthorId: string;
listComment: any;
setListComment: (value: any) => void;
countComment: number;
setCountComment: (value: number) => void;
}) { }) {
const { user } = useAuth();
const handlePress = (item: any) => { const handlePress = (item: any) => {
if (item.label === "Hapus") { if (item.label === "Hapus") {
setShowDeleteAlert(true); AlertDefaultSystem({
title: "Hapus",
message: "Apakah Anda yakin ingin menghapus komentar ini?",
textLeft: "Batal",
textRight: "Hapus",
onPressLeft: () => {},
onPressRight: async () => {
try {
const response = await apiForumDeleteComment({ id: commentId });
if (response.success) {
setListComment(
listComment.filter((item: any) => item.id !== commentId)
);
setCountComment(countComment - 1);
Toast.show({
type: "success",
text1: "Berhasil dihapus",
});
} else {
Toast.show({
type: "error",
text1: response.message,
});
}
} catch (error) {
console.log("[ERROR]", error);
}
},
});
} else { } else {
router.push(item.path as any); router.push(item.path as any);
} }
@@ -24,7 +72,11 @@ export default function Forum_MenuDrawerCommentar({
return ( return (
<> <>
<MenuDrawerDynamicGrid <MenuDrawerDynamicGrid
data={drawerItemsForumComentar({ id })} data={
commentAuthorId === user?.id
? drawerItemsForumComentarForAuthor({ id })
: drawerItemsForumComentarForNonAuthor({ id })
}
columns={4} columns={4}
onPressItem={handlePress} onPressItem={handlePress}
/> />

View File

@@ -4,21 +4,30 @@ import { listDummyReportForum } from "@/lib/dummy-data/forum/report-list";
import { useState } from "react"; import { useState } from "react";
import { View } from "react-native"; import { View } from "react-native";
export default function Forum_ReportListSection() { export default function Forum_ReportListSection({
const [value, setValue] = useState<any | number>(""); listMaster,
selectReport,
setSelectReport,
}: {
listMaster: any[] | null;
selectReport: string;
setSelectReport: (value: string) => void;
}) {
return ( return (
<> <>
<BaseBox> <BaseBox>
<StackCustom> <StackCustom>
<RadioGroup value={value} onChange={setValue}> <RadioGroup value={selectReport} onChange={(val) => {
{listDummyReportForum.map((e, i) => ( setSelectReport(val);
}}>
{listMaster?.map((e, i) => (
<View key={i}> <View key={i}>
<RadioCustom <RadioCustom
label={e.title} label={e.title}
// value={i} // value={i}
value={e.title} value={e.id}
/> />
<TextCustom>{e.desc}</TextCustom> <TextCustom>{e.deskripsi}</TextCustom>
</View> </View>
))} ))}
</RadioGroup> </RadioGroup>

View File

@@ -10,10 +10,12 @@ import { View } from "react-native";
export default function Invesment_BoxDetailDataSection({ export default function Invesment_BoxDetailDataSection({
title, title,
imageId,
data, data,
bottomSection, bottomSection,
}: { }: {
title?: string; title?: string;
imageId?: string;
data: any; data: any;
bottomSection?: React.ReactNode; bottomSection?: React.ReactNode;
}) { }) {
@@ -21,14 +23,14 @@ export default function Invesment_BoxDetailDataSection({
<> <>
<BaseBox paddingBottom={0}> <BaseBox paddingBottom={0}>
<StackCustom gap={"xs"}> <StackCustom gap={"xs"}>
<DummyLandscapeImage /> <DummyLandscapeImage imageId={imageId} />
<Spacing /> <Spacing />
<TextCustom align="center" size="xlarge" bold> <TextCustom align="center" size="xlarge" bold>
{title || "Judul Investasi"} {title || "Judul Investasi"}
</TextCustom> </TextCustom>
<Spacing /> <Spacing />
{data.map((item: any, index: any) => ( {data?.map((item: any, index: any) => (
<Grid key={index}> <Grid key={index}>
<Grid.Col span={4}> <Grid.Col span={4}>
<TextCustom bold>{item.label}</TextCustom> <TextCustom bold>{item.label}</TextCustom>

View File

@@ -1,22 +1,54 @@
import { AlertDefaultSystem, ButtonCustom, Grid } from "@/components"; import { AlertDefaultSystem, ButtonCustom, Grid } from "@/components";
import {
apiInvestmentDelete,
apiInvestmentUpdateStatus,
} from "@/service/api-client/api-investment";
import { deleteImageService } from "@/service/upload-service";
import { router } from "expo-router"; import { router } from "expo-router";
import { useState } from "react";
import Toast from "react-native-toast-message";
export default function Investment_ButtonStatusSection({ export default function Investment_ButtonStatusSection({
id,
status, status,
buttonPublish buttonPublish,
}: { }: {
id: string;
status: string; status: string;
buttonPublish?: React.ReactNode; buttonPublish?: React.ReactNode;
}) { }) {
const [isLoading, setIsLoading] = useState(false);
const handleBatalkanReview = () => { const handleBatalkanReview = () => {
AlertDefaultSystem({ AlertDefaultSystem({
title: "Batalkan Review", title: "Batalkan Review",
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(); setIsLoading(true);
const response = await apiInvestmentUpdateStatus({
id: id as string,
status: "draft",
});
console.log("[RESPONSE]", JSON.stringify(response, null, 2));
if (response.success) {
Toast.show({
type: "success",
text1: "Berhasil Batalkan Review",
});
router.back();
} else {
Toast.show({
type: "error",
text1: "Gagal Batalkan Review",
});
}
} catch (error) {
console.log("[ERROR]", error);
} finally {
setIsLoading(false);
}
}, },
}); });
}; };
@@ -27,9 +59,31 @@ export default function Investment_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(); setIsLoading(true);
const response = await apiInvestmentUpdateStatus({
id: id as string,
status: "review",
});
console.log("[RESPONSE]", JSON.stringify(response, null, 2));
if (response.success) {
Toast.show({
type: "success",
text1: "Berhasil Ajukan Review",
});
router.back();
} else {
Toast.show({
type: "error",
text1: "Gagal Ajukan Review",
});
}
} catch (error) {
console.log("[ERROR]", error);
} finally {
setIsLoading(false);
}
}, },
}); });
}; };
@@ -40,9 +94,31 @@ export default function Investment_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(); setIsLoading(true);
const response = await apiInvestmentUpdateStatus({
id: id as string,
status: "draft",
});
console.log("[RESPONSE]", JSON.stringify(response, null, 2));
if (response.success) {
Toast.show({
type: "success",
text1: "Berhasil Update Status",
});
router.back();
} else {
Toast.show({
type: "error",
text1: "Gagal Update Status",
});
}
} catch (error) {
console.log("[ERROR]", error);
} finally {
setIsLoading(false);
}
}, },
}); });
}; };
@@ -53,9 +129,55 @@ export default function Investment_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(); setIsLoading(true);
const response = await apiInvestmentDelete({
id: id as string,
});
console.log("[RESPONSE]", JSON.stringify(response, null, 2));
if (response.success) {
const deleteImage = await deleteImageService({
id: response?.data?.imageId as string,
});
if (!deleteImage.success) {
Toast.show({
type: "error",
text1: "Gagal Hapus Data",
});
return;
}
const deleteFile = await deleteImageService({
id: response?.data?.prospektusFileId as string,
});
if (!deleteFile.success) {
Toast.show({
type: "error",
text1: "Gagal Hapus Data",
});
return;
}
Toast.show({
type: "success",
text1: "Berhasil Hapus Data",
});
router.back();
} else {
Toast.show({
type: "error",
text1: "Gagal Hapus Data",
});
}
} catch (error) {
console.log("[ERROR]", error);
} finally {
setIsLoading(false);
}
}, },
}); });
}; };
@@ -64,6 +186,7 @@ export default function Investment_ButtonStatusSection({
return ( return (
<> <>
<ButtonCustom <ButtonCustom
isLoading={isLoading}
backgroundColor="red" backgroundColor="red"
textColor="white" textColor="white"
onPress={handleOpenDeleteAlert} onPress={handleOpenDeleteAlert}
@@ -76,13 +199,11 @@ export default function Investment_ButtonStatusSection({
switch (status) { switch (status) {
case "publish": case "publish":
return <> return <>{buttonPublish}</>;
{buttonPublish}
</>;
case "review": case "review":
return ( return (
<ButtonCustom onPress={handleBatalkanReview}> <ButtonCustom isLoading={isLoading} onPress={handleBatalkanReview}>
Batalkan Review Batalkan Review
</ButtonCustom> </ButtonCustom>
); );
@@ -92,7 +213,7 @@ export default function Investment_ButtonStatusSection({
<> <>
<Grid> <Grid>
<Grid.Col span={6} style={{ paddingRight: 10 }}> <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>
@@ -108,7 +229,7 @@ export default function Investment_ButtonStatusSection({
<> <>
<Grid> <Grid>
<Grid.Col span={6} style={{ paddingRight: 10 }}> <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>

View File

@@ -10,26 +10,32 @@ import Investment_ButtonStatusSection from "./ButtonStatusSection";
export default function Invesment_DetailDataPublishSection({ export default function Invesment_DetailDataPublishSection({
status, status,
data,
bottomSection, bottomSection,
buttonSection, buttonSection,
}: { }: {
status: string; status: string;
data: any;
bottomSection?: React.ReactNode; bottomSection?: React.ReactNode;
buttonSection?: React.ReactNode; buttonSection?: React.ReactNode;
}) { }) {
// console.log("[DATA DETAIL]", JSON.stringify(data, null, 2));
return ( return (
<> <>
<StackCustom gap={"sm"}> <StackCustom gap={"sm"}>
<Invesment_BoxProgressSection status={status as string} /> <Invesment_BoxProgressSection status={status as string} />
<Invesment_BoxDetailDataSection <Invesment_BoxDetailDataSection
title={data?.title}
imageId={data?.imageId}
data={ data={
status === "publish" status === "publish"
? listDataPublishInvesment ? listDataPublishInvesment({ data })
: listDataNotPublishInvesment : listDataNotPublishInvesment({ data })
} }
bottomSection={bottomSection} bottomSection={bottomSection}
/> />
<Investment_ButtonStatusSection <Investment_ButtonStatusSection
id={data?.id}
status={status as string} status={status as string}
buttonPublish={buttonSection} buttonPublish={buttonSection}
/> />

View File

@@ -1,44 +1,43 @@
import { BaseBox, Grid, Spacing, TextCustom } from "@/components"; import { BaseBox, Grid, Spacing, TextCustom } from "@/components";
import API_IMAGE from "@/constants/api-storage";
import DUMMY_IMAGE from "@/constants/dummy-image-value"; import DUMMY_IMAGE from "@/constants/dummy-image-value";
import { formatCurrencyDisplay } from "@/utils/formatCurrencyDisplay";
import { Image } from "expo-image"; import { Image } from "expo-image";
import { Href } from "expo-router"; import { Href } from "expo-router";
import { View } from "react-native"; import { View } from "react-native";
interface Investment_StatusBoxProps { interface Investment_StatusBoxProps {
id: string; data: any;
status: string; status: string;
href?: Href href?: Href;
} }
export default function Investment_StatusBox({ export default function Investment_StatusBox({
id, data,
status, status,
href href,
}: Investment_StatusBoxProps) { }: Investment_StatusBoxProps) {
return ( return (
<BaseBox paddingTop={7} paddingBottom={7} href={href}> <BaseBox paddingTop={7} paddingBottom={7} href={href}>
<Grid> <Grid>
<Grid.Col span={6}> <Grid.Col span={6}>
<TextCustom truncate={2}> <TextCustom truncate={2}>{data?.title || ""}</TextCustom>
Title here : {status} Lorem ipsum dolor sit amet consectetur
adipisicing elit. Omnis, exercitationem, sequi enim quod distinctio
maiores laudantium amet, quidem atque repellat sit vitae qui aliquam
est veritatis laborum eum voluptatum totam!
</TextCustom>
<Spacing /> <Spacing />
<TextCustom bold size="small"> <TextCustom bold size="small">
Target Dana: Target Dana:
</TextCustom> </TextCustom>
<TextCustom>Rp. 7.500.000</TextCustom> <TextCustom truncate>
Rp. {formatCurrencyDisplay(data?.targetDana) || ""}
</TextCustom>
</Grid.Col> </Grid.Col>
<Grid.Col span={1}> <Grid.Col span={1}>
<View /> <View />
</Grid.Col> </Grid.Col>
<Grid.Col span={5}> <Grid.Col span={5}>
<Image <Image
source={DUMMY_IMAGE.background} source={API_IMAGE.GET({ fileId: data?.imageId })}
style={{ width: "auto", height: 100, borderRadius: 10 }} style={{ width: "auto", height: 100, borderRadius: 10 }}
/> />
</Grid.Col> </Grid.Col>

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

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

View File

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

View File

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

View File

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

View File

@@ -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>
</> </>

Some files were not shown because too many files have changed in this diff Show More