Compare commits

..

16 Commits

Author SHA1 Message Date
5f05d1f7f0 Component
Fix: fix nama upload dan delete file service

Invesment:
Fix:
- Edit data dan edit prospektus
- View file dengan metode react-native-webview

### No issue
2025-09-30 17:30:07 +08:00
3d8d8568a3 Invesment
Fix: create & edit list master sudah terintegrasi ke API

### No Issue
2025-09-30 11:00:31 +08:00
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
109 changed files with 7192 additions and 1719 deletions

View File

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

View File

@@ -1,9 +1,13 @@
import { BackButton, ViewWrapper } from "@/components";
import { MainColor } from "@/constants/color-palet";
import { FontAwesome } from "@expo/vector-icons";
import { Stack } from "expo-router";
import { BackButton } from "@/components";
import PdfViewer from "@/components/_ShareComponent/PdfViewer";
import API_STRORAGE from "@/constants/base-url-api-strorage";
import { Stack, useLocalSearchParams } from "expo-router";
import { SafeAreaView } from "react-native-safe-area-context";
export default function FileScreen() {
const { id } = useLocalSearchParams();
const url = API_STRORAGE.GET({ fileId: id as string });
return (
<>
<Stack.Screen
@@ -12,14 +16,9 @@ export default function FileScreen() {
headerLeft: () => <BackButton />,
}}
/>
<ViewWrapper>
<FontAwesome
name="file-pdf-o"
size={300}
style={{ alignSelf: "center" }}
color={MainColor.white}
/>
</ViewWrapper>
<SafeAreaView style={{ flex: 1 }} edges={["bottom"]}>
<PdfViewer uri={url} />
</SafeAreaView>
</>
);
}

View File

@@ -118,14 +118,13 @@ export default function UserLayout() {
headerLeft: () => <BackButton />,
}}
/>
<Stack.Screen
{/* <Stack.Screen
name="collaboration/[id]/detail-participant"
options={{
title: "Partisipasi Proyek",
headerLeft: () => <BackButton />,
}}
/>
/> */}
<Stack.Screen
name="collaboration/[id]/edit"
options={{
@@ -133,6 +132,20 @@ export default function UserLayout() {
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 ========= */}
@@ -509,6 +522,13 @@ export default function UserLayout() {
headerLeft: () => <BackButton />,
}}
/>
<Stack.Screen
name="job/[id]/archive"
options={{
title: "Arsip Job",
headerLeft: () => <BackButton />,
}}
/>
{/* ========== 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 { 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 { useFocusEffect } from "expo-router";
import _ from "lodash";
import { useState, useCallback } from "react";
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 (
<ViewWrapper hideFooter>
{Array.from({ length: 10 }).map((_, index) => (
<BaseBox
key={index}
paddingBlock={5}
href={`/collaboration/${index}/${generateProjectName()}/room-chat`}
>
<Grid>
<Grid.Col span={10}>
<TextCustom bold>{generateProjectName()}</TextCustom>
<TextCustom size="small">2 Anggota</TextCustom>
</Grid.Col>
<Grid.Col
span={2}
style={{ alignItems: "flex-end", justifyContent: "center" }}
{loadingGetData ? (
<LoaderCustom />
) : _.isEmpty(listData) ? (
<TextCustom align="center">Tidak ada data</TextCustom>
) : (
<StackCustom>
{listData?.map((item: any, index: any) => (
<BaseBox
key={index}
paddingBlock={5}
href={`/collaboration/${item?.ProjectCollaboration_RoomChat?.id}/${item?.ProjectCollaboration_RoomChat?.name}/room-chat`}
>
<Feather name="chevron-right" size={20} color={MainColor.white} />
</Grid.Col>
</Grid>
</BaseBox>
))}
<Grid>
<Grid.Col span={10}>
<TextCustom bold>
{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>
);
}
function generateProjectName() {
const adjectives = [
"Blue",
@@ -65,4 +123,4 @@ function generateProjectName() {
const randomNoun = nouns[Math.floor(Math.random() * nouns.length)];
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 { 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() {
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 (
<>
<ViewWrapper
@@ -15,13 +47,19 @@ export default function CollaborationBeranda() {
/>
}
>
{Array.from({ length: 10 }).map((_, index) => (
<Collaboration_BoxPublishSection
key={index}
id={index.toString()}
href={`/collaboration/${index}`}
/>
))}
{loadingGetData ? (
<LoaderCustom />
) : _.isEmpty(listData) ? (
<TextCustom align="center">Tidak ada data</TextCustom>
) : (
listData?.map((item: any, index: number) => (
<Collaboration_BoxPublishSection
key={index}
href={`/collaboration/${item.id}`}
data={item}
/>
))
)}
</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 { AccentColor, MainColor } from "@/constants/color-palet";
import { useAuth } from "@/hooks/use-auth";
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";
export default function CollaborationParticipans() {
const [activeCategory, setActiveCategory] = useState<string | null>(
"participant"
const [activeCategory, setActiveCategory] = useState<
"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) => {
setActiveCategory(item);
// tambahkan logika lain seperti filter dsb.
@@ -41,13 +72,13 @@ export default function CollaborationParticipans() {
<Spacing width={"2%"} />
<ButtonCustom
backgroundColor={
activeCategory === "main" ? MainColor.yellow : AccentColor.blue
activeCategory === "my-project" ? MainColor.yellow : AccentColor.blue
}
textColor={
activeCategory === "main" ? MainColor.black : MainColor.white
activeCategory === "my-project" ? MainColor.black : MainColor.white
}
style={{ width: "49%" }}
onPress={() => handlePress("main")}
onPress={() => handlePress("my-project")}
>
Proyek Saya
</ButtonCustom>
@@ -56,22 +87,27 @@ export default function CollaborationParticipans() {
return (
<ViewWrapper hideFooter headerComponent={headerComponent}>
{Array.from({ length: 10 }).map((_, index) => (
<Collaboration_BoxPublishSection
key={index.toString()}
id={index.toString()}
username={` ${
activeCategory === "participant"
? "Partisipasi Proyek"
: "Proyek Saya"
}`}
href={
activeCategory === "participant"
? `/collaboration/${index}/detail-participant`
: `/collaboration/${index}/detail-project-main`
}
/>
))}
{loadingGetData ? (
<LoaderCustom />
) : _.isEmpty(listData) ? (
<TextCustom align="center">Tidak ada data</TextCustom>
) : activeCategory === "participant" ? (
listData?.map((item: any, index: number) => (
<Collaboration_BoxPublishSection
key={index.toString()}
data={item?.ProjectCollaboration}
href={`/collaboration/${item?.ProjectCollaboration?.id}/detail-participant`}
/>
))
) : (
listData?.map((item: any, index: number) => (
<Collaboration_BoxPublishSection
key={index.toString()}
data={item}
href={`/collaboration/${item?.id}/detail-project-main`}
/>
))
)}
</ViewWrapper>
);
}

View File

@@ -1,17 +1,41 @@
/* eslint-disable react-hooks/exhaustive-deps */
/* eslint-disable @typescript-eslint/no-unused-vars */
import {
AvatarUsernameAndOtherComponent,
BackButton,
BaseBox,
BoxWithHeaderSection,
Grid,
StackCustom,
TextCustom,
ViewWrapper,
} 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() {
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 (
<>
<Stack.Screen
@@ -24,7 +48,7 @@ export default function CollaborationRoomInfo() {
<ViewWrapper>
<BoxWithHeaderSection>
<StackCustom>
{listData.map((item, index) => (
{listData({ data }).map((item, index) => (
<Grid key={index}>
<Grid.Col span={4}>
<TextCustom bold>{item.title}</TextCustom>
@@ -37,37 +61,42 @@ export default function CollaborationRoomInfo() {
</StackCustom>
</BoxWithHeaderSection>
<BoxWithHeaderSection>
{Array.from({ length: 10 }).map((_, index) => (
<AvatarUsernameAndOtherComponent key={index} avatarHref={`/profile/${index}`} />
))}
</BoxWithHeaderSection>
<BaseBox>
<StackCustom gap={10}>
{data?.ProjectCollaboration_AnggotaRoomChat?.map(
(item: any, index: number) => (
<AvatarUsernameAndOtherComponent
key={index}
avatarHref={`/profile/${item?.User?.Profile?.id}`}
name={item?.User?.username}
avatar={item?.User?.Profile?.imageId}
/>
)
)}
</StackCustom>
</BaseBox>
</ViewWrapper>
</>
);
}
const listData = [
const listData = ({ data }: { data: any }) => [
{
title: "Judul Proyek",
value: "Judul Proyek",
value: data?.ProjectCollaboration?.title || "-",
},
{
title: "Industri",
value: "Pilihan Industri",
},
{
title: "Deskripsi",
value: "Deskripsi Proyek",
value:
data?.ProjectCollaboration?.ProjectCollaborationMaster_Industri?.name ||
"-",
},
{
title: "Tujuan Proyek",
value:
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
value: data?.ProjectCollaboration?.purpose || "-",
},
{
title: "Keuntungan Proyek",
value:
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
value: data?.ProjectCollaboration?.benefit || "-",
},
];

View File

@@ -1,68 +1,13 @@
import {
BackButton,
BoxButtonOnFooter,
Grid,
TextCustom,
TextInputCustom,
ViewWrapper,
} from "@/components";
import { AccentColor, MainColor } from "@/constants/color-palet";
import { BackButton } from "@/components";
import { MainColor } from "@/constants/color-palet";
import { ICON_SIZE_SMALL } from "@/constants/constans-value";
import ChatScreen from "@/screens/Collaboration/GroupChatSection";
import { Feather } from "@expo/vector-icons";
import { router, Stack, useLocalSearchParams } from "expo-router";
import { StyleSheet, TouchableOpacity, View } from "react-native";
export default function CollaborationRoomChat() {
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 (
<>
<Stack.Screen
@@ -79,114 +24,8 @@ export default function CollaborationRoomChat() {
),
}}
/>
<ViewWrapper footerComponent={inputChat()}>
{dummyData.map((item, index) => (
<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>
<ChatScreen id={id as string} />
</>
);
}
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 {
AvatarUsernameAndOtherComponent,
BaseBox,
BackButton,
DotButton,
DrawerCustom,
StackCustom,
TextCustom,
ViewWrapper,
MenuDrawerDynamicGrid,
ViewWrapper
} from "@/components";
import { ICON_SIZE_SMALL } from "@/constants/constans-value";
import Collaboration_BoxDetailSection from "@/screens/Collaboration/BoxDetailSection";
import { MaterialIcons } from "@expo/vector-icons";
import { useLocalSearchParams } from "expo-router";
import { useState } from "react";
import { apiCollaborationGetOne } from "@/service/api-client/api-collaboration";
import { Ionicons } from "@expo/vector-icons";
import { router, Stack, useFocusEffect, useLocalSearchParams } from "expo-router";
import { useCallback, useState } from "react";
export default function CollaborationDetailParticipant() {
const { id } = useLocalSearchParams();
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 (
<>
<Stack.Screen
options={{
title: "Detail Proyek",
headerLeft: () => <BackButton />,
headerRight: () => (
<DotButton onPress={() => setOpenDrawerParticipant(true)} />
),
}}
/>
<ViewWrapper>
<Collaboration_BoxDetailSection id={id as string} />
<BaseBox style={{ height: 500 }}>
<Collaboration_BoxDetailSection data={data} />
{/* <BaseBox style={{ height: 500 }}>
<TextCustom align="center" bold size="large">
Partisipan
</TextCustom>
@@ -39,13 +67,33 @@ export default function CollaborationDetailParticipant() {
}
/>
))}
</BaseBox>
</BaseBox> */}
</ViewWrapper>
<DrawerCustom
isVisible={openDrawerParticipant}
closeDrawer={() => setOpenDrawerParticipant(false)}
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>
<TextCustom bold>Deskripsi Diri</TextCustom>
@@ -56,7 +104,7 @@ export default function CollaborationDetailParticipant() {
Temporibus iusto soluta necessitatibus.
</TextCustom>
</StackCustom>
</DrawerCustom>
</DrawerCustom> */}
</>
);
}

View File

@@ -1,30 +1,65 @@
/* eslint-disable react-hooks/exhaustive-deps */
import {
AlertDefaultSystem,
BackButton,
ButtonCustom,
DotButton,
DrawerCustom,
LoaderCustom,
MenuDrawerDynamicGrid,
Spacing,
StackCustom,
TextCustom,
ViewWrapper,
ViewWrapper
} from "@/components";
import { IconEdit } from "@/components/_Icon";
import Collaboration_BoxDetailSection from "@/screens/Collaboration/BoxDetailSection";
import Collaboration_MainParticipanSelectedSection from "@/screens/Collaboration/ProjectMainSelectedSection";
import { router, Stack, useLocalSearchParams } from "expo-router";
import { useState } from "react";
import {
apiCollaborationGetOne
} 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() {
const { id } = useLocalSearchParams();
const [openDrawer, setOpenDrawer] = useState(false);
const [openDrawerParticipant, setOpenDrawerParticipant] = useState(false);
const [selected, setSelected] = useState<(string | number)[]>([]);
const [data, setData] = useState<any>();
const [loadingGetData, setLoadingGetData] = useState(false);
const handleEdit = () => {
console.log("Edit collaboration");
router.push("/(application)/(user)/collaboration/(id)/edit");
useFocusEffect(
useCallback(() => {
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 (
@@ -37,34 +72,21 @@ export default function CollaborationDetailProjectMain() {
}}
/>
<ViewWrapper>
<Collaboration_BoxDetailSection id={id as string} />
<Collaboration_MainParticipanSelectedSection
selected={selected}
setSelected={setSelected}
setOpenDrawerParticipant={setOpenDrawerParticipant}
/>
{loadingGetData ? (
<LoaderCustom />
) : (
<>
<Collaboration_BoxDetailSection data={data} />
{/* <Collaboration_MainParticipanSelectedSection
selected={selected}
setSelected={setSelected}
setOpenDrawerParticipant={setOpenDrawerParticipant}
listData={listData as any}
/> */}
<ButtonCustom
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 />
<Spacing />
</>
)}
</ViewWrapper>
<DrawerCustom
@@ -76,31 +98,20 @@ export default function CollaborationDetailProjectMain() {
data={[
{
label: "Edit",
path: "/(application)/(user)/collaboration/(tabs)/group",
path: `/(application)/(user)/collaboration/${id}/edit`,
icon: <IconEdit />,
},
{
label: "Pilih Partisipan",
path: `/(application)/(user)/collaboration/${id}/select-of-participants`,
icon: <MaterialIcons name="checklist" size={24} color="white" />,
},
]}
onPressItem={(item) => {
handleEdit();
onPressItem={(item: any) => {
handleSubmit(item);
}}
/>
</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 {
ButtonCustom,
LoaderCustom,
SelectCustom,
StackCustom,
TextAreaCustom,
TextInputCustom,
ViewWrapper,
} 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() {
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 (
<ViewWrapper>
<StackCustom gap={"xs"}>
<TextInputCustom label="Judul" placeholder="Masukan judul" required />
<TextInputCustom label="Lokasi" placeholder="Masukan lokasi" required />
<SelectCustom
label="Pilih Industri"
data={[
{ label: "Industri 1", value: "industri-1" },
{ label: "Industri 2", value: "industri-2" },
{ label: "Industri 3", value: "industri-3" },
]}
onChange={(value) => console.log(value)}
/>
{loadingData ? (
<LoaderCustom />
) : (
<StackCustom gap={"xs"}>
<TextInputCustom
label="Judul"
placeholder="Masukan judul"
required
value={data?.title}
onChangeText={(value) => setData({ ...data, title: 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
required
label="Tujuan Proyek"
placeholder="Masukan tujuan proyek"
showCount
maxLength={1000}
/>
<TextAreaCustom
required
label="Tujuan Proyek"
placeholder="Masukan tujuan proyek"
showCount
maxLength={1000}
value={data?.purpose}
onChangeText={(value) => setData({ ...data, purpose: value })}
/>
<TextAreaCustom
required
label="Keuntungan Proyek"
placeholder="Masukan keuntungan proyek"
showCount
maxLength={1000}
/>
<TextAreaCustom
required
label="Keuntungan Proyek"
placeholder="Masukan keuntungan proyek"
showCount
maxLength={1000}
value={data?.benefit}
onChangeText={(value) => setData({ ...data, benefit: value })}
/>
<ButtonCustom
title="Update"
onPress={() => {
console.log("Update proyek");
router.back();
}}
/>
</StackCustom>
<ButtonCustom
isLoading={isLoading}
title="Update"
onPress={() => {
handlerSubmitUpdate();
}}
/>
</StackCustom>
)}
</ViewWrapper>
);
}

View File

@@ -1,22 +1,75 @@
/* eslint-disable react-hooks/exhaustive-deps */
import {
AlertDefaultSystem,
BackButton,
ButtonCustom,
DotButton,
DrawerCustom,
InformationBox,
LoaderCustom,
MenuDrawerDynamicGrid,
TextAreaCustom,
ViewWrapper,
} from "@/components";
import { useAuth } from "@/hooks/use-auth";
import Collaboration_BoxDetailSection from "@/screens/Collaboration/BoxDetailSection";
import {
apiCollaborationGetOne,
apiCollaborationGetParticipants,
} from "@/service/api-client/api-collaboration";
import { Ionicons } from "@expo/vector-icons";
import { router, Stack, useLocalSearchParams } from "expo-router";
import { useState } from "react";
import {
router,
Stack,
useFocusEffect,
useLocalSearchParams,
} from "expo-router";
import { useCallback, useState } from "react";
export default function CollaborationDetail() {
const { user } = useAuth();
const { id } = useLocalSearchParams();
const [openDrawerPartisipasi, setOpenDrawerPartisipasi] = useState(false);
const [data, setData] = useState<any>();
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 (
<>
<Stack.Screen
@@ -29,15 +82,35 @@ export default function CollaborationDetail() {
}}
/>
<ViewWrapper>
<Collaboration_BoxDetailSection id={id as string} />
<ButtonCustom onPress={() => setOpenDrawerPartisipasi(true)}>
Partisipasi
</ButtonCustom>
{!data && !isParticipant ? (
<LoaderCustom />
) : (
<>
{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>
{/* Drawer Partisipasi */}
<DrawerCustom
{/* <DrawerCustom
isVisible={openDrawerPartisipasi}
closeDrawer={() => setOpenDrawerPartisipasi(false)}
height={300}
@@ -48,6 +121,8 @@ export default function CollaborationDetail() {
required
showCount
maxLength={500}
value={description}
onChangeText={setDescription}
/>
<ButtonCustom
@@ -58,19 +133,21 @@ export default function CollaborationDetail() {
message: "Apakah anda sudah yakin ingin menyimpan data ini ?",
textLeft: "Batal",
textRight: "Simpan",
onPressRight: () => router.replace(`/collaboration/(tabs)/group`),
onPressRight: () => {
handlerSubmitParticipans();
},
});
}}
>
Simpan
</ButtonCustom>
</DrawerCustom>
</DrawerCustom> */}
{/* Drawer Menu */}
<DrawerCustom
isVisible={openDrawerMenu}
closeDrawer={() => setOpenDrawerMenu(false)}
height={250}
height={"auto"}
>
<MenuDrawerDynamicGrid
data={[

View File

@@ -1,38 +1,79 @@
/* eslint-disable react-hooks/exhaustive-deps */
import {
AvatarUsernameAndOtherComponent,
BaseBox,
DrawerCustom,
Spacing,
StackCustom,
TextCustom,
ViewWrapper
AvatarUsernameAndOtherComponent,
BaseBox,
DrawerCustom,
LoaderCustom,
Spacing,
StackCustom,
TextCustom,
ViewWrapper,
} from "@/components";
import { apiCollaborationGetParticipants } from "@/service/api-client/api-collaboration";
import { Feather } from "@expo/vector-icons";
import { useLocalSearchParams } from "expo-router";
import { useState } from "react";
import _ from "lodash";
import { useEffect, useState } from "react";
import { ScrollView } from "react-native";
export default function CollaborationListOfParticipants() {
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);
return (
<>
<ViewWrapper>
{Array.from({ length: 10 }).map((_, index) => (
<BaseBox key={index} paddingBlock={5}>
<AvatarUsernameAndOtherComponent
avatarHref={`/profile/${id}`}
rightComponent={
<Feather
name="chevron-right"
size={24}
color="white"
onPress={() => setOpenDrawer(true)}
/>
}
/>
</BaseBox>
))}
{loadingGetData ? (
<LoaderCustom />
) : _.isEmpty(listData) ? (
<TextCustom align="center">Tidak ada partisipan</TextCustom>
) : (
listData?.map((item: any, index: number) => (
<BaseBox key={index} paddingBlock={5}>
<AvatarUsernameAndOtherComponent
avatar={item?.User?.Profile?.imageId}
avatarHref={`/profile/${item?.User?.Profile?.id}`}
name={item?.User?.username}
rightComponent={
<Feather
name="chevron-right"
size={24}
color="white"
onPress={() => {
setDescription(item?.deskripsi_diri);
setOpenDrawer(true);
}}
/>
}
/>
</BaseBox>
))
)}
</ViewWrapper>
{/* Drawer */}
@@ -44,34 +85,7 @@ export default function CollaborationListOfParticipants() {
<TextCustom bold>Deskripsi diri</TextCustom>
<BaseBox>
<ScrollView style={{ height: "80%" }}>
<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>
<TextCustom>{description}</TextCustom>
</ScrollView>
</BaseBox>
<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 {
ButtonCustom,
SelectCustom,
StackCustom,
TextAreaCustom,
TextInputCustom,
ViewWrapper
ButtonCustom,
LoaderCustom,
SelectCustom,
StackCustom,
TextAreaCustom,
TextInputCustom,
ViewWrapper,
} 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 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() {
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 (
<ViewWrapper>
<StackCustom gap={"xs"}>
<TextInputCustom label="Judul" placeholder="Masukan judul" required />
<TextInputCustom label="Lokasi" placeholder="Masukan lokasi" required />
<SelectCustom
label="Pilih Industri"
data={[
{ label: "Industri 1", value: "industri-1" },
{ label: "Industri 2", value: "industri-2" },
{ label: "Industri 3", value: "industri-3" },
]}
onChange={(value) => console.log(value)}
/>
{loadingMaster ? (
<LoaderCustom />
) : (
<StackCustom gap={"xs"}>
<TextInputCustom
label="Judul"
placeholder="Masukan judul"
required
value={data?.title}
onChangeText={(value: any) => setData({ ...data, title: value })}
/>
<TextAreaCustom
required
label="Tujuan Proyek"
placeholder="Masukan tujuan proyek"
showCount
maxLength={1000}
/>
<TextInputCustom
label="Lokasi"
placeholder="Masukan lokasi"
required
value={data?.lokasi}
onChangeText={(value: any) => setData({ ...data, lokasi: value })}
/>
<TextAreaCustom
required
label="Keuntungan Proyek"
placeholder="Masukan keuntungan proyek"
showCount
maxLength={1000}
/>
<SelectCustom
label="Pilih Industri"
data={listMaster?.map((item: any) => ({
label: item.name,
value: item.id,
}))}
value={data?.projectCollaborationMaster_IndustriId}
onChange={(value: any) => {
console.log(value);
setData({
...data,
projectCollaborationMaster_IndustriId: value,
});
}}
/>
<ButtonCustom
title="Simpan"
onPress={() => {
console.log("Simpan proyek");
router.back();
}}
/>
</StackCustom>
<TextAreaCustom
required
label="Tujuan Proyek"
placeholder="Masukan tujuan proyek"
showCount
maxLength={1000}
value={data?.purpose}
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>
);
}

View File

@@ -35,6 +35,7 @@ export default function EventContribution() {
category: "contribution",
userId: user?.id,
});
console.log("[DATA] ", JSON.stringify(response.data, null, 2));
if (response.success) {
setListData(response.data);
@@ -70,9 +71,9 @@ export default function EventContribution() {
>
<StackCustom>
<AvatarUsernameAndOtherComponent
avatar={item?.User?.Profile?.imageId}
avatarHref={`/profile/${item?.User?.Profile?.id}`}
name={item?.User?.username}
avatar={item?.Event?.Author?.Profile?.imageId}
avatarHref={`/profile/${item?.Event?.Author?.Profile?.id}`}
name={item?.Event?.Author?.username}
rightComponent={
<TextCustom truncate>
{dateTimeView({
@@ -83,10 +84,10 @@ export default function EventContribution() {
}
/>
<TextCustom bold align="center" size="xlarge">
<TextCustom bold align="center" size="xlarge" truncate={2}>
{item?.Event?.title}
</TextCustom>
<Spacing height={10} />
<Spacing height={0} />
{/* <Grid>
{item?.Event?.Event_Peserta?.map(

View File

@@ -1,37 +1,98 @@
import {
BoxButtonOnFooter,
ButtonCustom,
LoaderCustom,
TextAreaCustom,
ViewWrapper,
} from "@/components";
import { router } from "expo-router";
import { useState } from "react";
import { apiForumGetOne, apiForumUpdate } from "@/service/api-client/api-forum";
import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
import { useCallback, useState } from "react";
import Toast from "react-native-toast-message";
export default function ForumEdit() {
const { id } = useLocalSearchParams();
const [text, setText] = useState("");
const [loadingGetData, setLoadingGetData] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const buttonFooter = (
<BoxButtonOnFooter>
<ButtonCustom
onPress={() => {
console.log("Posting", text);
router.back();
}}
>
Update
</ButtonCustom>
</BoxButtonOnFooter>
useFocusEffect(
useCallback(() => {
onLoadData(id as string);
}, [id])
);
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 (
<ViewWrapper footerComponent={buttonFooter}>
<TextAreaCustom
placeholder="Ketik diskusi anda..."
maxLength={1000}
showCount
value={text}
onChangeText={setText}
/>
<ViewWrapper footerComponent={buttonFooter()}>
{!loadingGetData ? (
<TextAreaCustom
placeholder="Ketik diskusi anda..."
maxLength={1000}
showCount
value={text}
onChangeText={(value) => {
setText(value);
}}
/>
) : (
<LoaderCustom />
)}
</ViewWrapper>
);
}

View File

@@ -1,35 +1,86 @@
/* eslint-disable react-hooks/exhaustive-deps */
import {
AlertCustom,
AvatarCustom,
AvatarComp,
ButtonCustom,
CenterCustom,
DrawerCustom,
FloatingButton,
Grid,
LoaderCustom,
StackCustom,
TextCustom,
ViewWrapper,
} from "@/components";
import { MainColor } from "@/constants/color-palet";
import { useAuth } from "@/hooks/use-auth";
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 { useLocalSearchParams } from "expo-router";
import { useState } from "react";
import { apiForumGetAll } from "@/service/api-client/api-forum";
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() {
const { id } = useLocalSearchParams();
const { user } = useAuth();
const [openDrawer, setOpenDrawer] = useState(false);
const [status, setStatus] = useState("");
const [alertStatus, setAlertStatus] = useState(false);
const [deleteAlert, setDeleteAlert] = useState(false);
const [listData, setListData] = useState<any | null>(null);
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 (
<>
<ViewWrapper>
<ViewWrapper
floatingButton={
user?.id === id && (
<FloatingButton
onPress={() =>
router.navigate("/(application)/(user)/forum/create")
}
/>
)
}
>
<StackCustom>
<CenterCustom>
<AvatarCustom
href={`/(application)/(image)/preview-image/${id}`}
<AvatarComp
fileId={dataUser?.Profile?.imageId}
href={`/(application)/(image)/preview-image/${dataUser?.Profile?.imageId}`}
size="xl"
/>
</CenterCustom>
@@ -37,32 +88,43 @@ export default function Forumku() {
<Grid>
<Grid.Col span={6}>
<TextCustom bold truncate>
@bagas_banuna
@{dataUser?.username || "-"}
</TextCustom>
<TextCustom>1 postingan</TextCustom>
<TextCustom>{listData?.length || "0"} postingan</TextCustom>
</Grid.Col>
<Grid.Col span={6} style={{ alignItems: "flex-end" }}>
<ButtonCustom href={`/profile/${id}`}>
<ButtonCustom href={`/profile/${dataUser?.Profile?.id}`}>
Kunjungi Profile
</ButtonCustom>
</Grid.Col>
</Grid>
{listDummyDiscussionForum.map((e, i) => (
<Forum_BoxDetailSection
key={i}
data={e}
setOpenDrawer={setOpenDrawer}
setStatus={setStatus}
isTruncate={true}
href={`/forum/${id}`}
/>
))}
{loadingGetList ? (
<LoaderCustom />
) : _.isEmpty(listData) ? (
<TextCustom> Tidak ada diskusi</TextCustom>
) : (
<>
{listData?.map((item: any, index: number) => (
<Forum_BoxDetailSection
isRightComponent={false}
key={index}
data={item}
isTruncate={true}
href={`/forum/${item.id}`}
onSetData={(value) => {
setOpenDrawer(value.setOpenDrawer);
setStatus(value.setStatus);
}}
/>
))}
</>
)}
</StackCustom>
</ViewWrapper>
{/* Drawer Komponen Eksternal */}
<DrawerCustom
height={350}
height={"auto"}
isVisible={openDrawer}
closeDrawer={() => setOpenDrawer(false)}
>
@@ -72,42 +134,9 @@ export default function Forumku() {
setIsDrawerOpen={() => {
setOpenDrawer(false);
}}
setShowDeleteAlert={setDeleteAlert}
setShowAlertStatus={setAlertStatus}
authorId={id as string}
/>
</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 {
AlertCustom,
ButtonCustom,
DrawerCustom,
LoaderCustom,
Spacing,
TextAreaCustom,
TextCustom,
ViewWrapper,
} from "@/components";
import { MainColor } from "@/constants/color-palet";
import { useAuth } from "@/hooks/use-auth";
import Forum_CommentarBoxSection from "@/screens/Forum/CommentarBoxSection";
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_MenuDrawerCommentar from "@/screens/Forum/MenuDrawerSection.tsx/MenuCommentar";
import { router, useLocalSearchParams } from "expo-router";
import { useState } from "react";
import {
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() {
const { id } = useLocalSearchParams();
console.log(id);
const { user } = useAuth();
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 [alertStatus, setAlertStatus] = useState(false);
const [deleteAlert, setDeleteAlert] = useState(false);
const [text, setText] = useState("");
const [authorId, setAuthorId] = useState("");
const [dataId, setDataId] = useState("");
// Comentar
const [openDrawerCommentar, setOpenDrawerCommentar] = useState(false);
const [alertDeleteCommentar, setAlertDeleteCommentar] = useState(false);
const [commentId, setCommentId] = useState("");
const [commentAuthorId, setCommentAuthorId] = useState("");
const dataDummy = {
name: "Bagas",
status: "Open",
date: "14/07/2025",
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,
useFocusEffect(
useCallback(() => {
onLoadData(id as string);
}, [id])
);
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 (
<>
<ViewWrapper>
{/* <StackCustom>
</StackCustom> */}
<Forum_BoxDetailSection
data={dataDummy}
setOpenDrawer={setOpenDrawer}
setStatus={setStatus}
/>
{!data && !listComment ? (
<LoaderCustom />
) : (
<>
{/* Box Posting */}
<Forum_BoxDetailSection
data={data}
onSetData={() => {
setOpenDrawer(true);
setStatus(data.ForumMaster_StatusPosting?.status);
setAuthorId(data.Author?.id);
setDataId(data.id);
}}
/>
<TextAreaCustom
placeholder="Ketik diskusi anda..."
maxLength={1000}
showCount
value={text}
onChangeText={setText}
style={{
marginBottom: 0,
}}
/>
<ButtonCustom
style={{
alignSelf: "flex-end",
}}
onPress={() => {
console.log("Posting", text);
router.back();
}}
>
Balas
</ButtonCustom>
{/* Area Commentar */}
{data?.ForumMaster_StatusPosting?.status === "Open" && (
<>
<TextAreaCustom
placeholder="Ketik diskusi anda..."
maxLength={1000}
showCount
value={text}
onChangeText={setText}
style={{
marginBottom: 0,
}}
/>
<ButtonCustom
isLoading={isLoadingComment}
style={{
alignSelf: "flex-end",
}}
onPress={() => {
handlerCreateCommentar();
}}
>
Balas
</ButtonCustom>
</>
)}
<Spacing height={40} />
<Spacing height={40} />
{listDummyCommentarForum.map((e, i) => (
<Forum_CommentarBoxSection
key={i}
data={e}
setOpenDrawer={setOpenDrawerCommentar}
/>
))}
{/* List Commentar */}
{_.isEmpty(listComment) ? (
<TextCustom align="center" color="gray" size={"small"}>
Tidak ada komentar
</TextCustom>
) : (
<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>
{/* Posting Drawer */}
<DrawerCustom
height={350}
height={"auto"}
isVisible={openDrawer}
closeDrawer={() => setOpenDrawer(false)}
>
<Forum_MenuDrawerBerandaSection
id={id as string}
id={dataId}
status={status}
setIsDrawerOpen={() => {
setOpenDrawer(false);
}}
setShowDeleteAlert={setDeleteAlert}
setShowAlertStatus={setAlertStatus}
authorId={authorId}
handlerUpdateStatus={(value: any) => {
handlerUpdateStatus(value);
}}
/>
</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}
/>
{/* Commentar */}
{/* Commentar Drawer */}
<DrawerCustom
height={350}
height={"auto"}
isVisible={openDrawerCommentar}
closeDrawer={() => setOpenDrawerCommentar(false)}
>
<Forum_MenuDrawerCommentar
id={id as string}
id={commentId as string}
commentId={commentId}
commentAuthorId={commentAuthorId}
setIsDrawerOpen={() => {
setOpenDrawerCommentar(false);
}}
setShowDeleteAlert={setAlertDeleteCommentar}
listComment={listComment}
setListComment={setListComment}
countComment={data?.count}
setCountComment={(val: any) => {
setData((prev: any) => ({
...prev,
count: val,
}));
}}
/>
</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,
} from "@/components";
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() {
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 = (
<BoxButtonOnFooter>
<ButtonCustom
disabled={!value}
backgroundColor={MainColor.red}
textColor={MainColor.white}
onPress={() => {
console.log("Report lainnya");
router.back();
handlerSubmitReport();
}}
>
Report
</ButtonCustom>
</BoxButtonOnFooter>
);
return (
<>
<ViewWrapper footerComponent={handleSubmit}>
<TextAreaCustom placeholder="Laporkan Komentar" />
<TextAreaCustom
placeholder="Laporkan Komentar"
value={value}
onChangeText={setValue}
/>
</ViewWrapper>
</>
);

View File

@@ -5,17 +5,54 @@ import {
ViewWrapper,
} from "@/components";
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() {
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 = (
<BoxButtonOnFooter>
<ButtonCustom
disabled={!value}
backgroundColor={MainColor.red}
textColor={MainColor.white}
onPress={() => {
console.log("Report lainnya");
router.back();
handlerSubmitReport();
}}
>
Report
@@ -25,7 +62,11 @@ export default function ForumOtherReportPosting() {
return (
<>
<ViewWrapper footerComponent={handleSubmit}>
<TextAreaCustom placeholder="Laporkan Diskusi" />
<TextAreaCustom
placeholder="Laporkan Diskusi"
value={value}
onChangeText={setValue}
/>
</ViewWrapper>
</>
);

View File

@@ -1,41 +1,105 @@
import {
ButtonCustom,
Spacing,
StackCustom,
ViewWrapper
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 { 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() {
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 (
<>
<ViewWrapper>
<StackCustom>
<Forum_ReportListSection />
<ButtonCustom
backgroundColor={MainColor.red}
textColor={MainColor.white}
onPress={() => {
console.log("Report");
router.back();
}}
>
Report
</ButtonCustom>
<ButtonCustom
backgroundColor={AccentColor.blue}
textColor={MainColor.white}
onPress={() => {
console.log("Lainnya");
router.replace("/forum/[id]/other-report-commentar");
}}
>
Lainnya
</ButtonCustom>
<Spacing/>
</StackCustom>
{isLoadingList ? (
<LoaderCustom />
) : (
<StackCustom>
<Forum_ReportListSection
listMaster={listMaster}
selectReport={selectReport}
setSelectReport={setSelectReport}
/>
<ButtonCustom
disabled={!selectReport}
backgroundColor={MainColor.red}
textColor={MainColor.white}
onPress={() => {
handlerReport();
}}
>
Report
</ButtonCustom>
<ButtonCustom
backgroundColor={AccentColor.blue}
textColor={MainColor.white}
onPress={() => {
router.replace(`/forum/${id}/other-report-commentar`);
}}
>
Lainnya
</ButtonCustom>
<Spacing />
</StackCustom>
)}
</ViewWrapper>
</>
);

View File

@@ -1,20 +1,103 @@
import { ViewWrapper, StackCustom, ButtonCustom, Spacing } from "@/components";
import { MainColor, AccentColor } from "@/constants/color-palet";
import {
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 { 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() {
return (
<>
<ViewWrapper>
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 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>
<Forum_ReportListSection />
<Forum_ReportListSection
listMaster={listMaster}
selectReport={selectReport}
setSelectReport={setSelectReport}
/>
<ButtonCustom
disabled={!selectReport}
backgroundColor={MainColor.red}
textColor={MainColor.white}
onPress={() => {
console.log("Report");
router.back();
AlertDefaultSystem({
title: "Laporan Posting",
message: "Apakah anda yakin ingin melaporkan postingan ini?",
textLeft: "Batal",
textRight: "Laporkan",
onPressRight: () => {
handlerReport();
},
});
}}
>
Report
@@ -23,15 +106,15 @@ export default function ForumReportPosting() {
backgroundColor={AccentColor.blue}
textColor={MainColor.white}
onPress={() => {
console.log("Lainnya");
router.replace("/forum/[id]/other-report-posting");
router.replace(`/forum/${id}/other-report-posting`);
}}
>
Lainnya
</ButtonCustom>
<Spacing />
</StackCustom>
</ViewWrapper>
</>
);
}
)}
</ViewWrapper>
</>
);
}

View File

@@ -4,18 +4,47 @@ import {
TextAreaCustom,
ViewWrapper,
} from "@/components";
import { useAuth } from "@/hooks/use-auth";
import { apiForumCreate } from "@/service/api-client/api-forum";
import { router } from "expo-router";
import { useState } from "react";
import Toast from "react-native-toast-message";
export default function ForumCreate() {
const { user } = useAuth();
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 = (
<BoxButtonOnFooter>
<ButtonCustom
isLoading={isLoading}
onPress={() => {
console.log("Posting", text);
router.back();
handlerSubmit();
}}
>
Posting

View File

@@ -1,25 +1,58 @@
/* eslint-disable react-hooks/exhaustive-deps */
import {
AlertCustom,
AvatarCustom,
AvatarComp,
BackButton,
DrawerCustom,
LoaderCustom,
SearchInput,
TextCustom,
ViewWrapper,
} from "@/components";
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 { listDummyDiscussionForum } from "@/screens/Forum/list-data-dummy";
import Forum_MenuDrawerBerandaSection from "@/screens/Forum/MenuDrawerSection.tsx/MenuBeranda";
import { router, Stack } from "expo-router";
import { useState } from "react";
import { apiForumGetAll } from "@/service/api-client/api-forum";
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() {
const id = "test-id-forum";
const [openDrawer, setOpenDrawer] = useState(false);
const [status, setStatus] = useState("");
const [alertStatus, setAlertStatus] = useState(false);
const [deleteAlert, setDeleteAlert] = useState(false);
const { user } = useAuth();
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 (
<>
@@ -27,12 +60,23 @@ export default function Forum() {
options={{
title: "Forum",
headerLeft: () => <BackButton />,
headerRight: () => <AvatarCustom href={`/forum/${id}/forumku`} />,
headerRight: () => (
<AvatarComp
fileId={dataUser?.Profile?.imageId}
size="base"
href={`/forum/${user?.id}/forumku`}
/>
),
}}
/>
<ViewWrapper
headerComponent={<SearchInput placeholder="Cari topik diskusi" />}
headerComponent={
<SearchInput
placeholder="Cari topik diskusi"
onChangeText={(e) => setSearch(e)}
/>
}
floatingButton={
<FloatingButton
onPress={() =>
@@ -41,73 +85,45 @@ export default function Forum() {
/>
}
>
{listDummyDiscussionForum.map((e, i) => (
<Forum_BoxDetailSection
key={i}
data={e}
setOpenDrawer={setOpenDrawer}
setStatus={setStatus}
isTruncate={true}
href={`/forum/${id}`}
/>
))}
{loadingGetList ? (
<LoaderCustom />
) : _.isEmpty(listData) ? (
<TextCustom align="center" color="gray">
Tidak ada diskusi
</TextCustom>
) : (
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>
<DrawerCustom
height={350}
height={"auto"}
isVisible={openDrawer}
closeDrawer={() => setOpenDrawer(false)}
>
<Forum_MenuDrawerBerandaSection
id={id}
id={dataId}
authorId={authorId}
status={status}
setIsDrawerOpen={() => {
setOpenDrawer(false);
}}
setShowDeleteAlert={setDeleteAlert}
setShowAlertStatus={setAlertStatus}
/>
</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 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() {
const { user } = useAuth();
const [activeCategory, setActiveCategory] = useState<string | null>(
"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) => {
setActiveCategory(item.value);
// tambahkan logika lain seperti filter dsb.
@@ -26,14 +61,20 @@ export default function InvestmentPortofolio() {
);
return (
<ViewWrapper headerComponent={scrollComponent} hideFooter>
{Array.from({ length: 10 }).map((_, index) => (
<Investment_StatusBox
key={index}
id={index.toString()}
status={activeCategory as string}
href={`/investment/${index}/${activeCategory}/detail`}
/>
))}
{loadingList ? (
<LoaderCustom />
) : _.isEmpty(listData) ? (
<TextCustom align="center">Tidak ada data {activeCategory}</TextCustom>
) : (
listData.map((item: any, index: number) => (
<Investment_StatusBox
key={index}
data={item}
status={activeCategory as string}
href={`/investment/${item.id}/${activeCategory}/detail`}
/>
))
)}
</ViewWrapper>
);
}

View File

@@ -1,3 +1,4 @@
/* eslint-disable react-hooks/exhaustive-deps */
import {
BackButton,
DotButton,
@@ -12,16 +13,42 @@ import { ICON_SIZE_MEDIUM } from "@/constants/constans-value";
import Investment_ButtonInvestasiSection from "@/screens/Invesment/ButtonInvestasiSection";
import Invesment_ComponentBoxOnBottomDetail from "@/screens/Invesment/ComponentBoxOnBottomDetail";
import Invesment_DetailDataPublishSection from "@/screens/Invesment/DetailDataPublishSection";
import { apiInvestmentGetById } from "@/service/api-client/api-investment";
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 { useState } from "react";
import { useCallback, useState } from "react";
export default function InvestmentDetailStatus() {
const { id, status } = useLocalSearchParams();
const [openDrawerDraft, setOpenDrawerDraft] = 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,
});
setData(response.data);
} catch (error) {
console.log("[ERROR]", error);
}
};
const handlePressDraft = (item: IMenuDrawerItem) => {
console.log("PATH >> ", item.path);
router.navigate(item.path as any);
@@ -36,7 +63,8 @@ export default function InvestmentDetailStatus() {
const bottomSection = (
<Invesment_ComponentBoxOnBottomDetail
id={id as string}
id={data?.id}
prospectusId={data?.prospektusFileId}
status={status as string}
/>
);
@@ -63,6 +91,7 @@ export default function InvestmentDetailStatus() {
<ViewWrapper>
<Invesment_DetailDataPublishSection
status={status as string}
data={data}
bottomSection={bottomSection}
buttonSection={buttonSection}
/>

View File

@@ -1,3 +1,4 @@
/* eslint-disable react-hooks/exhaustive-deps */
import {
BaseBox,
BoxButtonOnFooter,
@@ -5,38 +6,149 @@ import {
ButtonCustom,
CenterCustom,
InformationBox,
LoaderCustom,
Spacing,
StackCustom,
TextCustom,
ViewWrapper,
} from "@/components";
import { MainColor } from "@/constants/color-palet";
import { FontAwesome5 } from "@expo/vector-icons";
import { router } from "expo-router";
import DIRECTORY_ID from "@/constants/directory-id";
import {
apiInvestmentGetById,
apiInvestmentUpdateData,
} from "@/service/api-client/api-investment";
import { deleteFileService, uploadFileService } from "@/service/upload-service";
import pickFile, { IFileData } from "@/utils/pickFile";
import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
import _ from "lodash";
import { useCallback, useState } from "react";
import Toast from "react-native-toast-message";
export default function InvestmentEditProspectus() {
const { id } = useLocalSearchParams();
const [data, setData] = useState<any>(null);
const [loadingGet, setLoadingGet] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const [pdf, setPdf] = useState<IFileData | null>(null);
useFocusEffect(
useCallback(() => {
onLoadData();
}, [id])
);
const onLoadData = async () => {
try {
setLoadingGet(true);
const response = await apiInvestmentGetById({
id: id as string,
});
setData(response.data);
} catch (error) {
console.log("[ERROR]", error);
} finally {
setLoadingGet(false);
}
};
const handleSubmitUpdate = async () => {
const prevProspectusFileId = data?.prospektusFileId;
try {
setIsLoading(true);
const responseUploadImage = await uploadFileService({
imageUri: pdf?.uri as any,
dirId: DIRECTORY_ID.investasi_prospektus,
});
if (!responseUploadImage.success) {
Toast.show({
type: "error",
text1: "Gagal mengunggah gambar",
});
return;
}
const prospektusFileId = responseUploadImage.data.id;
const responseUpdate = await apiInvestmentUpdateData({
id: id as string,
data: prospektusFileId,
category: "prospectus",
});
if (responseUpdate.success) {
const deletePrevImage = await deleteFileService({
id: prevProspectusFileId as any,
});
if (!deletePrevImage.success) {
console.log("[ERROR DELETE PREV IMAGE]", deletePrevImage.message);
return;
}
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);
}
};
const buttonFooter = (
<BoxButtonOnFooter>
<ButtonCustom onPress={() => router.back()}>Update</ButtonCustom>
<ButtonCustom
disabled={pdf === null}
isLoading={isLoading}
onPress={handleSubmitUpdate}
>
Update
</ButtonCustom>
</BoxButtonOnFooter>
);
return (
<ViewWrapper footerComponent={buttonFooter}>
<ViewWrapper footerComponent={!loadingGet && buttonFooter}>
<StackCustom gap={"xs"}>
<InformationBox text="File prospektus wajib untuk diupload, agar calon investor paham dengan prospek investasi yang akan anda jalankan kedepan." />
<Spacing />
<BaseBox>
<CenterCustom>
<FontAwesome5
name="file-pdf"
size={30}
color={MainColor.disabled}
/>
{loadingGet ? (
<LoaderCustom />
) : pdf ? (
<TextCustom truncate>{pdf.name}</TextCustom>
) : (
<TextCustom truncate>
{_.snakeCase(data?.title || "").replace(/_/g, "-")}.pdf
</TextCustom>
)}
</CenterCustom>
</BaseBox>
<ButtonCenteredOnly
disabled={loadingGet}
icon="upload"
onPress={() => router.push("/(application)/(image)/take-picture/123")}
onPress={() => {
pickFile({
allowedType: "pdf",
setPdfUri(file: any) {
setPdf({
uri: file.uri,
name: file.name,
size: file.size,
});
},
});
}}
>
Upload
</ButtonCenteredOnly>

View File

@@ -1,40 +1,224 @@
/* eslint-disable react-hooks/exhaustive-deps */
import {
ButtonCenteredOnly,
ButtonCustom,
InformationBox,
LandscapeFrameUploaded,
LoaderCustom,
SelectCustom,
Spacing,
StackCustom,
TextInputCustom,
ViewWrapper
ViewWrapper,
} from "@/components";
import dummyPembagianDeviden from "@/lib/dummy-data/investment/pembagian-deviden";
import dummyListPencarianInvestor from "@/lib/dummy-data/investment/pencarian-investor";
import dummyPeriodeDeviden from "@/lib/dummy-data/investment/periode-deviden";
import { router } from "expo-router";
import { useState } from "react";
import API_STRORAGE from "@/constants/base-url-api-strorage";
import DIRECTORY_ID from "@/constants/directory-id";
import {
apiInvestmentGetById,
apiInvestmentUpdateData,
} from "@/service/api-client/api-investment";
import { apiMasterInvestment } from "@/service/api-client/api-master";
import {
deleteFileService,
uploadFileService,
} from "@/service/upload-service";
import { formatCurrencyDisplay } from "@/utils/formatCurrencyDisplay";
import pickFile from "@/utils/pickFile";
import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
import _ from "lodash";
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() {
const [data, setData] = useState({
const { id } = useLocalSearchParams();
const [data, setData] = useState<IInvestment>({
title: "",
targetDana: 0,
hargaPerLembar: 0,
totalLembar: 0,
rasioKeuntungan: 0,
pencarianInvestor: "",
periodeDeviden: "",
pembagianDeviden: "",
targetDana: "",
hargaLembar: "",
totalLembar: "",
roi: "",
masterPencarianInvestorId: "",
masterPeriodeDevidenId: "",
masterPembagianDevidenId: "",
authorId: "",
imageId: "",
prospektusFileId: "",
});
const [image, setImage] = useState<string | null>(null);
const [isLoading, setIsLoading] = useState(false);
const [loadingMaster, setLoadingMaster] = useState(false);
const [listPencarianInvestor, setListPencarianInvestor] = useState<any[]>([]);
const [listPeriodeDeviden, setListPeriodeDeviden] = useState<any[]>([]);
const [listPembagianDeviden, setListPembagianDeviden] = useState<any[]>([]);
useFocusEffect(
useCallback(() => {
onLoadMaster();
onLoadData();
}, [id])
);
const onLoadMaster = async () => {
try {
setLoadingMaster(true);
const response = await apiMasterInvestment({ category: "" });
setListPencarianInvestor(response.data.pencarianInvestor);
setListPeriodeDeviden(response.data.periodeDeviden);
setListPembagianDeviden(response.data.pembagianDeviden);
} catch (error) {
console.log("[ERROR]", error);
} finally {
setLoadingMaster(false);
}
};
const onLoadData = async () => {
try {
const response = await apiInvestmentGetById({
id: id as string,
});
setData(response.data);
} catch (error) {
console.log("[ERROR]", error);
}
};
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 }));
};
const validateData = () => {
if (
!data.title ||
!data.targetDana ||
!data.hargaLembar ||
!data.totalLembar ||
!data.roi ||
!data.masterPencarianInvestorId ||
!data.masterPeriodeDevidenId ||
!data.masterPembagianDevidenId
) {
Toast.show({
type: "info",
text1: "Harap isi semua data",
});
return false;
}
return true;
};
const handleSubmitUpdate = async () => {
let newData = {
...data,
};
if (!validateData()) {
return;
}
try {
setIsLoading(true);
if (image) {
const responseUploadImage = await uploadFileService({
imageUri: image,
dirId: DIRECTORY_ID.investasi_image,
});
if (!responseUploadImage.success) {
Toast.show({
type: "error",
text1: "Gagal mengunggah gambar",
});
return;
}
const deletePrevImage = await deleteFileService({
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,
category: "data"
});
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 (
<ViewWrapper>
<StackCustom gap={"xs"}>
<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
icon="upload"
onPress={() => router.push("/take-picture/1")}
onPress={() => {
pickFile({
setImageUri: ({ uri }) => {
setImage(uri);
},
allowedType: "image",
});
}}
>
Upload
</ButtonCenteredOnly>
@@ -47,7 +231,7 @@ export default function InvestmentEdit() {
required
placeholder="Judul"
label="Judul"
value={data.title}
value={data?.title}
onChangeText={(value) => setData({ ...data, title: value })}
/>
@@ -57,22 +241,8 @@ export default function InvestmentEdit() {
placeholder="0"
label="Target Dana"
keyboardType="numeric"
onChangeText={(value) =>
setData({ ...data, targetDana: Number(value) })
}
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()}
onChangeText={handleChangeCurrency("targetDana")}
value={displayTargetDana}
/>
<TextInputCustom
@@ -81,10 +251,8 @@ export default function InvestmentEdit() {
placeholder="0"
label="Harga Per Lembar"
keyboardType="numeric"
onChangeText={(value) =>
setData({ ...data, targetDana: Number(value) })
}
value={data.targetDana === 0 ? "" : data.targetDana.toString()}
onChangeText={handleChangeCurrency("hargaLembar")}
value={displayHargaPerLembar}
/>
<TextInputCustom
@@ -92,10 +260,8 @@ export default function InvestmentEdit() {
placeholder="0"
label="Total Lembar"
keyboardType="numeric"
onChangeText={(value) =>
setData({ ...data, totalLembar: Number(value) })
}
value={data.totalLembar === 0 ? "" : data.totalLembar.toString()}
onChangeText={(value) => setData({ ...data, totalLembar: value })}
value={displayTotalLembar}
/>
<TextInputCustom
@@ -104,57 +270,78 @@ export default function InvestmentEdit() {
label="Rasio Keuntungan / ROI %"
placeholder="0"
keyboardType="numeric"
onChangeText={(value) =>
setData({ ...data, rasioKeuntungan: Number(value) })
}
value={
data.rasioKeuntungan === 0 ? "" : data.rasioKeuntungan.toString()
}
onChangeText={(value) => setData({ ...data, roi: value })}
value={data?.roi === "" ? "" : data?.roi}
/>
<SelectCustom
required
placeholder="Pilih batas waktu"
label="Pencarian Investor"
data={dummyListPencarianInvestor.map((item) => ({
label: item.name + `${" "}hari`,
value: item.id,
}))}
onChange={(value) =>
setData({ ...data, pencarianInvestor: value as any })
}
value={data.pencarianInvestor}
/>
{loadingMaster ? (
<LoaderCustom />
) : (
<SelectCustom
required
placeholder="Pilih batas waktu"
label="Pencarian Investor"
data={
_.isEmpty(listPencarianInvestor)
? []
: listPencarianInvestor.map((item) => ({
label: item.name + `${" "}hari`,
value: item.id,
}))
}
onChange={(value) =>
setData({ ...data, masterPencarianInvestorId: value as any })
}
value={data.masterPencarianInvestorId}
/>
)}
<SelectCustom
required
placeholder="Pilih batas waktu"
label="Pilih Periode Deviden"
data={dummyPeriodeDeviden.map((item) => ({
label: item.name,
value: item.id,
}))}
onChange={(value) =>
setData({ ...data, periodeDeviden: value as any })
}
value={data.periodeDeviden}
/>
{loadingMaster ? (
<LoaderCustom />
) : (
<SelectCustom
required
placeholder="Pilih batas waktu"
label="Pilih Periode Deviden"
data={
_.isEmpty(listPeriodeDeviden)
? []
: listPeriodeDeviden.map((item) => ({
label: item.name,
value: item.id,
}))
}
onChange={(value) =>
setData({ ...data, masterPeriodeDevidenId: value as any })
}
value={data.masterPeriodeDevidenId}
/>
)}
{loadingMaster ? (
<LoaderCustom />
) : (
<SelectCustom
required
placeholder="Pilih batas waktu"
label="Pilih Pembagian Deviden"
data={
_.isEmpty(listPembagianDeviden)
? []
: listPembagianDeviden.map((item) => ({
label: item.name + `${" "}bulan`,
value: item.id,
}))
}
onChange={(value) =>
setData({ ...data, masterPembagianDevidenId: value as any })
}
value={data.masterPembagianDevidenId}
/>
)}
<SelectCustom
required
placeholder="Pilih batas waktu"
label="Pilih Pembagian Deviden"
data={dummyPembagianDeviden.map((item) => ({
label: item.name + `${" "}bulan`,
value: item.id,
}))}
onChange={(value) =>
setData({ ...data, pembagianDeviden: value as any })
}
value={data.pembagianDeviden}
/>
<Spacing />
<ButtonCustom onPress={() => router.replace("/investment/portofolio")}>
<ButtonCustom isLoading={isLoading} onPress={handleSubmitUpdate}>
Simpan
</ButtonCustom>
</StackCustom>

View File

@@ -1,76 +1,236 @@
import {
BaseBox,
ButtonCenteredOnly,
ButtonCustom,
CenterCustom,
InformationBox,
LandscapeFrameUploaded,
SelectCustom,
Spacing,
StackCustom,
TextInputCustom,
ViewWrapper,
BaseBox,
ButtonCenteredOnly,
ButtonCustom,
CenterCustom,
InformationBox,
LandscapeFrameUploaded,
LoaderCustom,
SelectCustom,
Spacing,
StackCustom,
TextCustom,
TextInputCustom,
ViewWrapper,
} from "@/components";
import { MainColor } from "@/constants/color-palet";
import dummyPembagianDeviden from "@/lib/dummy-data/investment/pembagian-deviden";
import dummyListPencarianInvestor from "@/lib/dummy-data/investment/pencarian-investor";
import dummyPeriodeDeviden from "@/lib/dummy-data/investment/periode-deviden";
import DIRECTORY_ID from "@/constants/directory-id";
import { useAuth } from "@/hooks/use-auth";
import { apiInvestmentCreate } from "@/service/api-client/api-investment";
import { apiMasterInvestment } from "@/service/api-client/api-master";
import { uploadFileService } from "@/service/upload-service";
import { formatCurrencyDisplay } from "@/utils/formatCurrencyDisplay";
import pickFile, { IFileData } from "@/utils/pickFile";
import { FontAwesome5 } from "@expo/vector-icons";
import { router } from "expo-router";
import { useState } from "react";
import { router, useFocusEffect } from "expo-router";
import _ from "lodash";
import { useCallback, useState } from "react";
import Toast from "react-native-toast-message";
export default function InvestmentCreate() {
const { user } = useAuth();
const [data, setData] = useState({
title: "",
targetDana: 0,
hargaPerLembar: 0,
totalLembar: 0,
rasioKeuntungan: 0,
targetDana: "",
hargaPerLembar: "",
totalLembar: "",
rasioKeuntungan: "",
pencarianInvestor: "",
periodeDeviden: "",
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 [loadingMaster, setLoadingMaster] = useState(false);
const [listPencarianInvestor, setListPencarianInvestor] = useState<any[]>([]);
const [listPeriodeDeviden, setListPeriodeDeviden] = useState<any[]>([]);
const [listPembagianDeviden, setListPembagianDeviden] = useState<any[]>([]);
useFocusEffect(
useCallback(() => {
onLoadMaster();
}, [])
);
const onLoadMaster = async () => {
try {
setLoadingMaster(true);
const response = await apiMasterInvestment({ category: "" });
setListPencarianInvestor(response.data.pencarianInvestor);
setListPeriodeDeviden(response.data.periodeDeviden);
setListPembagianDeviden(response.data.pembagianDeviden);
} catch (error) {
console.log("[ERROR]", error);
} finally {
setLoadingMaster(false);
}
};
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 validateData = () => {
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 false;
}
return true;
};
const handleSubmit = async () => {
if (!validateData()) {
return;
}
if (!image || !pdf) {
Toast.show({
type: "error",
text1: "Harap upload gambar dan file PDF",
});
return;
}
try {
setIsLoading(true);
const responseUploadImage = await uploadFileService({
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 uploadFileService({
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 });
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 (
<ViewWrapper>
<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." />
<LandscapeFrameUploaded />
<LandscapeFrameUploaded image={image as string} />
<ButtonCenteredOnly
icon="upload"
onPress={() => router.push("/take-picture/1")}
onPress={() => {
pickFile({
setImageUri: ({ uri }) => {
setImage(uri);
},
allowedType: "image",
});
}}
>
Upload
</ButtonCenteredOnly>
<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>
<CenterCustom>
<FontAwesome5
name="file-pdf"
size={30}
color={MainColor.disabled}
/>
{pdf ? (
<TextCustom>{pdf.name}</TextCustom>
) : (
<FontAwesome5
name="file-pdf"
size={30}
color={MainColor.disabled}
/>
)}
</CenterCustom>
</BaseBox>
<ButtonCenteredOnly
icon="upload"
onPress={() => router.push("/take-picture/1")}
onPress={() => {
pickFile({
setPdfUri: ({ uri, name, size }) => {
setPdf({ uri, name, size });
},
allowedType: "pdf",
});
}}
>
Upload File
</ButtonCenteredOnly>
@@ -90,22 +250,8 @@ export default function InvestmentCreate() {
placeholder="0"
label="Target Dana"
keyboardType="numeric"
onChangeText={(value) =>
setData({ ...data, targetDana: Number(value) })
}
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()}
onChangeText={handleChangeCurrency("targetDana")}
value={displayTargetDana}
/>
<TextInputCustom
@@ -114,22 +260,24 @@ export default function InvestmentCreate() {
placeholder="0"
label="Harga Per Lembar"
keyboardType="numeric"
onChangeText={(value) =>
setData({ ...data, targetDana: Number(value) })
}
value={data.targetDana === 0 ? "" : data.targetDana.toString()}
onChangeText={handleChangeCurrency("hargaPerLembar")}
value={displayHargaPerLembar}
/>
<TextInputCustom
required
placeholder="0"
label="Total Lembar"
keyboardType="numeric"
onChangeText={(value) =>
setData({ ...data, totalLembar: Number(value) })
}
value={data.totalLembar === 0 ? "" : data.totalLembar.toString()}
/>
<StackCustom gap={0}>
<TextInputCustom
required
placeholder="0"
label="Total Lembar"
keyboardType="numeric"
// onChangeText={handleChangeCurrency("totalLembar")}
value={displayTotalLembar}
/>
<TextCustom size={"small"} color="gray">
*Total lembar dihitung dari, Target Dana / Harga Perlembar
</TextCustom>
</StackCustom>
<Spacing />
<TextInputCustom
required
@@ -137,57 +285,80 @@ export default function InvestmentCreate() {
label="Rasio Keuntungan / ROI %"
placeholder="0"
keyboardType="numeric"
onChangeText={(value) =>
setData({ ...data, rasioKeuntungan: Number(value) })
}
onChangeText={(value) => setData({ ...data, rasioKeuntungan: value })}
value={
data.rasioKeuntungan === 0 ? "" : data.rasioKeuntungan.toString()
data.rasioKeuntungan === "" ? "" : data.rasioKeuntungan.toString()
}
/>
<SelectCustom
required
placeholder="Pilih batas waktu"
label="Pencarian Investor"
data={dummyListPencarianInvestor.map((item) => ({
label: item.name + `${" "}hari`,
value: item.id,
}))}
onChange={(value) =>
setData({ ...data, pencarianInvestor: value as any })
}
value={data.pencarianInvestor}
/>
{loadingMaster ? (
<LoaderCustom />
) : (
<SelectCustom
required
placeholder="Pilih batas waktu"
label="Pencarian Investor"
data={
_.isEmpty(listPencarianInvestor)
? []
: listPencarianInvestor.map((item) => ({
label: item.name + `${" "}hari`,
value: item.id,
}))
}
onChange={(value) =>
setData({ ...data, pencarianInvestor: value as any })
}
value={data.pencarianInvestor}
/>
)}
<SelectCustom
required
placeholder="Pilih batas waktu"
label="Pilih Periode Deviden"
data={dummyPeriodeDeviden.map((item) => ({
label: item.name,
value: item.id,
}))}
onChange={(value) =>
setData({ ...data, periodeDeviden: value as any })
}
value={data.periodeDeviden}
/>
{loadingMaster ? (
<LoaderCustom />
) : (
<SelectCustom
required
placeholder="Pilih batas waktu"
label="Pilih Periode Deviden"
data={
_.isEmpty(listPeriodeDeviden)
? []
: listPeriodeDeviden.map((item) => ({
label: item.name,
value: item.id,
}))
}
onChange={(value) =>
setData({ ...data, periodeDeviden: value as any })
}
value={data.periodeDeviden}
/>
)}
{loadingMaster ? (
<LoaderCustom />
) : (
<SelectCustom
required
placeholder="Pilih batas waktu"
label="Pilih Pembagian Deviden"
data={
_.isEmpty(listPembagianDeviden)
? []
: listPembagianDeviden.map((item) => ({
label: item.name + `${" "}bulan`,
value: item.id,
}))
}
onChange={(value) =>
setData({ ...data, pembagianDeviden: value as any })
}
value={data.pembagianDeviden}
/>
)}
<SelectCustom
required
placeholder="Pilih batas waktu"
label="Pilih Pembagian Deviden"
data={dummyPembagianDeviden.map((item) => ({
label: item.name + `${" "}bulan`,
value: item.id,
}))}
onChange={(value) =>
setData({ ...data, pembagianDeviden: value as any })
}
value={data.pembagianDeviden}
/>
<Spacing />
<ButtonCustom onPress={() => router.replace("/investment/portofolio")}>
<ButtonCustom isLoading={isLoading} onPress={() => handleSubmit()}>
Simpan
</ButtonCustom>
</StackCustom>

View File

@@ -1,16 +1,57 @@
import { BaseBox, TextCustom, ViewWrapper } from "@/components";
import { jobDataDummy } from "@/screens/Job/listDataDummy";
/* eslint-disable react-hooks/exhaustive-deps */
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() {
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 (
<ViewWrapper hideFooter>
{jobDataDummy.map((e, i) => (
<BaseBox key={i} paddingTop={20} paddingBottom={20}>
<TextCustom align="center" bold truncate size="large">
{e.posisi}
</TextCustom>
</BaseBox>
))}
{isLoadData ? (
<LoaderCustom />
) : _.isEmpty(listData) ? (
<TextCustom align="center">Anda tidak memiliki arsip</TextCustom>
) : (
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>
);
}

View File

@@ -1,34 +1,83 @@
import {
AvatarUsernameAndOtherComponent,
BoxWithHeaderSection,
FloatingButton,
SearchInput,
Spacing,
TextCustom,
ViewWrapper
AvatarUsernameAndOtherComponent,
BoxWithHeaderSection,
FloatingButton,
LoaderCustom,
SearchInput,
Spacing,
StackCustom,
TextCustom,
ViewWrapper,
} from "@/components";
import { jobDataDummy } from "@/screens/Job/listDataDummy";
import { router } from "expo-router";
import { apiJobGetAll } from "@/service/api-client/api-job";
import { router, useFocusEffect } from "expo-router";
import _ from "lodash";
import { useCallback, useState } from "react";
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 (
<ViewWrapper
hideFooter
floatingButton={
<FloatingButton onPress={() => router.push("/job/create")} />
}
headerComponent={<SearchInput placeholder="Cari pekerjaan" />}
headerComponent={
<SearchInput placeholder="Cari pekerjaan" onChangeText={handleSearch} />
}
>
{jobDataDummy.map((item, index) => (
<BoxWithHeaderSection key={index} onPress={() => router.push(`/job/${item.id}`)}>
<AvatarUsernameAndOtherComponent avatarHref={`/profile/${item.id}`} />
<Spacing />
<TextCustom truncate={2} align="center" bold size="large">
{item.posisi}
</TextCustom>
<Spacing />
</BoxWithHeaderSection>
))}
{isLoadData ? (
<LoaderCustom />
) : _.isEmpty(listData) ? (
<TextCustom align="center">Belum ada lowongan</TextCustom>
) : (
listData.map((item, index) => (
<BoxWithHeaderSection
key={index}
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>
);
}

View File

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

View File

@@ -1,23 +1,51 @@
/* eslint-disable react-hooks/exhaustive-deps */
import {
BackButton,
DotButton,
DrawerCustom,
LoaderCustom,
MenuDrawerDynamicGrid,
Spacing,
StackCustom,
ViewWrapper,
} from "@/components";
import { IconEdit } from "@/components/_Icon";
import { IMenuDrawerItem } from "@/components/_Interface/types";
import Job_BoxDetailSection from "@/screens/Job/BoxDetailSection";
import Job_ButtonStatusSection from "@/screens/Job/ButtonStatusSection";
import { jobDataDummy } from "@/screens/Job/listDataDummy";
import { router, Stack, useLocalSearchParams } from "expo-router";
import { useState } from "react";
import { apiJobGetOne } from "@/service/api-client/api-job";
import {
router,
Stack,
useFocusEffect,
useLocalSearchParams,
} from "expo-router";
import { useCallback, useState } from "react";
export default function JobDetailStatus() {
const { id, status } = useLocalSearchParams();
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) => {
console.log("PATH >> ", item.path);
@@ -38,9 +66,23 @@ export default function JobDetailStatus() {
}}
/>
<ViewWrapper>
<Job_BoxDetailSection data={jobDetail} />
<Job_ButtonStatusSection status={status as string} />
<Spacing />
{isLoadData ? (
<LoaderCustom />
) : (
<>
<StackCustom>
<Job_BoxDetailSection data={data} />
<Job_ButtonStatusSection
id={id as string}
status={status as string}
isLoading={isLoading}
onSetLoading={setIsLoading}
isArchive={true}
/>
</StackCustom>
<Spacing />
</>
)}
</ViewWrapper>
<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 {
ButtonCenteredOnly,
ButtonCustom,
InformationBox,
LandscapeFrameUploaded,
Spacing,
StackCustom,
TextAreaCustom,
TextInputCustom,
ViewWrapper,
BaseBox,
ButtonCenteredOnly,
ButtonCustom,
DummyLandscapeImage,
InformationBox,
LandscapeFrameUploaded,
LoaderCustom,
Spacing,
StackCustom,
TextAreaCustom,
TextInputCustom,
ViewWrapper,
} from "@/components";
import { router } from "expo-router";
import DIRECTORY_ID from "@/constants/directory-id";
import { apiJobGetOne, apiJobUpdateData } from "@/service/api-client/api-job";
import {
deleteFileService,
uploadFileService,
} 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() {
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 uploadFileService({
imageUri: imageUri,
dirId: DIRECTORY_ID.job_image,
});
if (responseUploadImage.success) {
newImageId = responseUploadImage.data.id;
}
}
if (data?.imageId) {
const responseDeleteImage = await deleteFileService({
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 = () => {
return (
<>
<ButtonCustom onPress={() => router.back()}>Update</ButtonCustom>
<ButtonCustom isLoading={isLoading} onPress={() => handlerOnUpdate()}>
Update
</ButtonCustom>
<Spacing />
</>
);
@@ -23,45 +135,64 @@ export default function JobEdit() {
return (
<ViewWrapper>
<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." />
{isLoadData ? (
<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 />
<ButtonCenteredOnly
onPress={() => {
router.push("/(application)/(image)/take-picture/123");
}}
icon="upload"
>
Upload
</ButtonCenteredOnly>
{imageUri ? (
<LandscapeFrameUploaded image={imageUri as any} />
) : (
<BaseBox>
<DummyLandscapeImage imageId={data?.imageId} />
</BaseBox>
)}
<Spacing />
<ButtonCenteredOnly
onPress={() => {
pickImage({
setImageUri,
});
}}
icon="upload"
>
Upload
</ButtonCenteredOnly>
<TextInputCustom
label="Judul Lowongan"
placeholder="Masukan Judul Lowongan Kerja"
required
/>
<Spacing />
<TextAreaCustom
label="Syarat & Kualifikasi"
placeholder="Masukan Syarat & Kualifikasi Lowongan Kerja"
required
showCount
maxLength={1000}
/>
<TextInputCustom
label="Judul Lowongan"
placeholder="Masukan Judul Lowongan Kerja"
required
value={data.title}
onChangeText={(value) => setData({ ...data, title: value })}
/>
<TextAreaCustom
label="Deskripsi Lowongan"
placeholder="Masukan Deskripsi Lowongan Kerja"
required
showCount
maxLength={1000}
/>
<TextAreaCustom
label="Syarat & Kualifikasi"
placeholder="Masukan Syarat & Kualifikasi Lowongan Kerja"
required
showCount
maxLength={1000}
value={data.content}
onChangeText={(value) => setData({ ...data, content: value })}
/>
{buttonSubmit()}
</StackCustom>
<TextAreaCustom
label="Deskripsi Lowongan"
placeholder="Masukan Deskripsi Lowongan Kerja"
required
showCount
maxLength={1000}
value={data.deskripsi}
onChangeText={(value) => setData({ ...data, deskripsi: value })}
/>
{buttonSubmit()}
</StackCustom>
)}
</ViewWrapper>
);
}

View File

@@ -1,25 +1,43 @@
import {
ButtonCustom,
Spacing,
ViewWrapper
} from "@/components";
/* eslint-disable react-hooks/exhaustive-deps */
import { ButtonCustom, LoaderCustom, Spacing, StackCustom, ViewWrapper } from "@/components";
import { MainColor } from "@/constants/color-palet";
import { ICON_SIZE_SMALL } from "@/constants/constans-value";
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 * as Clipboard from "expo-clipboard";
import { useLocalSearchParams } from "expo-router";
import { useEffect, useState } from "react";
import { Alert, Linking } from "react-native";
export default function JobDetail() {
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 jobUrl =
"https://stg-hipmi.wibudev.com/job-vacancy/cm6ijt9w8005zucv4twsct657";
const onLoadData = async () => {
try {
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 supported = await Linking.canOpenURL(jobUrl);
@@ -44,9 +62,8 @@ export default function JobDetail() {
);
};
const CopyLinkButton = () => {
const jobUrl =
"https://stg-hipmi.wibudev.com/job-vacancy/cm6ijt9w8005zucv4twsct657";
const CopyLinkButton = ({ id }: { id: string }) => {
const jobUrl = `${linkUrl}${id}`;
const copyToClipboard = async () => {
await Clipboard.setStringAsync(jobUrl);
@@ -70,10 +87,18 @@ export default function JobDetail() {
return (
<ViewWrapper>
<Job_BoxDetailSection data={jobDetail}/>
<OpenLinkButton />
<Spacing />
<CopyLinkButton />
{isLoading ? (
<LoaderCustom />
) : (
<>
<Job_BoxDetailSection data={data} />
<StackCustom>
<OpenLinkButton id={id as string} />
<CopyLinkButton id={id as string} />
</StackCustom>
<Spacing />
</>
)}
</ViewWrapper>
);
}

View File

@@ -7,19 +7,99 @@ import {
StackCustom,
TextAreaCustom,
TextInputCustom,
ViewWrapper,
ViewWrapper
} from "@/components";
import DIRECTORY_ID from "@/constants/directory-id";
import { useAuth } from "@/hooks/use-auth";
import { apiJobCreate } from "@/service/api-client/api-job";
import { uploadFileService } from "@/service/upload-service";
import pickImage from "@/utils/pickImage";
import { router } from "expo-router";
import { useState } from "react";
import Toast from "react-native-toast-message";
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 uploadFileService({
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 = () => {
return (
<>
<ButtonCustom
onPress={() =>
router.replace("/(application)/(user)/job/(tabs)/status")
}
>
<ButtonCustom isLoading={isLoading} onPress={() => handlerOnSubmit()}>
Simpan
</ButtonCustom>
<Spacing />
@@ -32,10 +112,19 @@ export default function JobCreate() {
<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 />
{/* <BaseBox>
<Image
source={image ? { uri: image } : DUMMY_IMAGE.dummy_image}
style={{ width: "100%", height: 200 }}
/>
</BaseBox> */}
<LandscapeFrameUploaded image={image as string} />
<ButtonCenteredOnly
onPress={() => {
router.push("/(application)/(image)/take-picture/123");
// router.push("/(application)/(image)/take-picture/123");
pickImage({
setImageUri: setImage,
});
}}
icon="upload"
>
@@ -48,6 +137,8 @@ export default function JobCreate() {
label="Judul Lowongan"
placeholder="Masukan Judul Lowongan Kerja"
required
value={data.title}
onChangeText={(value) => setData({ ...data, title: value })}
/>
<TextAreaCustom
@@ -56,6 +147,8 @@ export default function JobCreate() {
required
showCount
maxLength={1000}
value={data.content}
onChangeText={(value) => setData({ ...data, content: value })}
/>
<TextAreaCustom
@@ -64,6 +157,8 @@ export default function JobCreate() {
required
showCount
maxLength={1000}
value={data.deskripsi}
onChangeText={(value) => setData({ ...data, deskripsi: value })}
/>
{buttonSubmit()}

View File

@@ -1,9 +1,9 @@
import {
BaseBox,
BoxButtonOnFooter,
ButtonCenteredOnly,
ButtonCustom,
ViewWrapper
BaseBox,
BoxButtonOnFooter,
ButtonCenteredOnly,
ButtonCustom,
ViewWrapper
} from "@/components";
import API_STRORAGE from "@/constants/base-url-api-strorage";
import DIRECTORY_ID from "@/constants/directory-id";
@@ -11,10 +11,10 @@ import DUMMY_IMAGE from "@/constants/dummy-image-value";
import { useAuth } from "@/hooks/use-auth";
import { apiFileDelete } from "@/service/api-client/api-file";
import {
apiGetOnePortofolio,
apiUpdatePortofolio,
apiGetOnePortofolio,
apiUpdatePortofolio,
} from "@/service/api-client/api-portofolio";
import { uploadImageService } from "@/service/upload-service";
import { uploadFileService } from "@/service/upload-service";
import pickImage from "@/utils/pickImage";
import { Image } from "expo-image";
import { router, useLocalSearchParams } from "expo-router";
@@ -45,7 +45,7 @@ export default function PortofolioEditLogo() {
try {
setIsLoading(true);
const response = await uploadImageService({
const response = await uploadFileService({
imageUri,
dirId: DIRECTORY_ID.portofolio_logo,
});

View File

@@ -77,18 +77,16 @@ export default function Profile() {
<>
<Stack.Screen
options={{
title: "Profile",
title: `Profile`,
headerLeft: () => <LeftButtonCustom />,
headerRight: () =>
isUserCheck() && (
<TouchableOpacity onPress={openDrawer}>
<Ionicons
name="ellipsis-vertical"
size={20}
color={MainColor.yellow}
/>
</TouchableOpacity>
),
headerRight: () => (
<ButtonnDot
id={id as string}
openDrawer={openDrawer}
isUserCheck={isUserCheck()}
logout={logout}
/>
),
headerStyle: GStyles.headerStyle,
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

@@ -1,8 +1,8 @@
import {
BaseBox,
BoxButtonOnFooter,
ButtonCenteredOnly,
ButtonCustom,
BaseBox,
BoxButtonOnFooter,
ButtonCenteredOnly,
ButtonCustom,
} from "@/components";
import ViewWrapper from "@/components/_ShareComponent/ViewWrapper";
import API_STRORAGE from "@/constants/base-url-api-strorage";
@@ -11,7 +11,7 @@ import DUMMY_IMAGE from "@/constants/dummy-image-value";
import { useAuth } from "@/hooks/use-auth";
import { apiFileDelete } from "@/service/api-client/api-file";
import { apiProfile, apiUpdateProfile } from "@/service/api-client/api-profile";
import { uploadImageService } from "@/service/upload-service";
import { uploadFileService } from "@/service/upload-service";
import { IProfile } from "@/types/Type-Profile";
import pickImage from "@/utils/pickImage";
import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
@@ -45,7 +45,7 @@ export default function UpdateBackgroundProfile() {
try {
setIsLoading(true);
const response = await uploadImageService({
const response = await uploadFileService({
imageUri,
dirId: DIRECTORY_ID.profile_background,
});

View File

@@ -8,16 +8,16 @@ import ViewWrapper from "@/components/_ShareComponent/ViewWrapper";
import API_STRORAGE from "@/constants/base-url-api-strorage";
import DIRECTORY_ID from "@/constants/directory-id";
import DUMMY_IMAGE from "@/constants/dummy-image-value";
import { useAuth } from "@/hooks/use-auth";
import { apiFileDelete } from "@/service/api-client/api-file";
import { apiProfile, apiUpdateProfile } from "@/service/api-client/api-profile";
import { uploadImageService } from "@/service/upload-service";
import { uploadFileService } from "@/service/upload-service";
import { IProfile } from "@/types/Type-Profile";
import pickImage from "@/utils/pickImage";
import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
import { useCallback, useState } from "react";
import { Image } from "react-native";
import Toast from "react-native-toast-message";
import { useAuth } from "@/hooks/use-auth";
import { apiFileDelete } from "@/service/api-client/api-file";
export default function UpdatePhotoProfile() {
const { id } = useLocalSearchParams();
@@ -46,7 +46,7 @@ export default function UpdatePhotoProfile() {
try {
setIsLoading(true);
const response = await uploadImageService({
const response = await uploadFileService({
imageUri,
dirId: DIRECTORY_ID.profile_foto,
});

View File

@@ -1,12 +1,12 @@
import {
BaseBox,
ButtonCenteredOnly,
ButtonCustom,
SelectCustom,
Spacing,
StackCustom,
TextInputCustom,
ViewWrapper,
BaseBox,
ButtonCenteredOnly,
ButtonCustom,
SelectCustom,
Spacing,
StackCustom,
TextInputCustom,
ViewWrapper,
} from "@/components";
import BoxButtonOnFooter from "@/components/Box/BoxButtonOnFooter";
import InformationBox from "@/components/Box/InformationBox";
@@ -15,7 +15,7 @@ import DUMMY_IMAGE from "@/constants/dummy-image-value";
import { useAuth } from "@/hooks/use-auth";
import { apiCreateProfile } from "@/service/api-client/api-profile";
import { apiValidationEmail } from "@/service/api-client/api-validation";
import { uploadImageService } from "@/service/upload-service";
import { uploadFileService } from "@/service/upload-service";
import pickImage from "@/utils/pickImage";
import { router } from "expo-router";
import { useState } from "react";
@@ -69,7 +69,7 @@ export default function CreateProfile() {
if (imagePhoto) {
try {
const responseUploadPhoto = await uploadImageService({
const responseUploadPhoto = await uploadFileService({
imageUri: imagePhoto,
dirId: DIRECTORY_ID.profile_foto,
});
@@ -90,7 +90,7 @@ export default function CreateProfile() {
if (imageBackground) {
try {
const responseUploadBackground = await uploadImageService({
const responseUploadBackground = await uploadFileService({
imageUri: imageBackground,
dirId: DIRECTORY_ID.profile_background,
});

View File

@@ -1,15 +1,57 @@
/* eslint-disable react-hooks/exhaustive-deps */
import {
LoaderCustom,
TextCustom,
ViewWrapper
} from "@/components";
import { useAuth } from "@/hooks/use-auth";
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() {
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 (
<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
data={item}
key={index}
href={`/voting/${index}/contribution`}
href={`/voting/${item.id}/contribution`}
/>
))}
</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 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() {
const { user } = useAuth();
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) => {
setActiveCategory(item);
// tambahkan logika lain seperti filter dsb.
@@ -25,13 +58,20 @@ export default function VotingHistory() {
/>
}
>
{Array.from({ length: 10 }).map((_, index) => (
<Voting_BoxPublishSection
key={index}
id={activeCategory as any}
href={`/voting/${index}/history`}
/>
))}
{loadingGetData ? (
<LoaderCustom />
) : _.isEmpty(listData) ? (
<TextCustom align="center">Tidak ada riwayat</TextCustom>
) : (
listData.map((item: any, index: number) => (
<Voting_BoxPublishSection
key={index}
id={item.id}
data={item}
href={`/voting/${item.id}/history`}
/>
))
)}
</ViewWrapper>
);
}

View File

@@ -1,23 +1,68 @@
/* eslint-disable react-hooks/exhaustive-deps */
import {
FloatingButton,
LoaderCustom,
SearchInput,
ViewWrapper
TextCustom,
ViewWrapper,
} from "@/components";
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() {
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 (
<ViewWrapper
hideFooter
floatingButton={
<FloatingButton onPress={() => router.push("/voting/create")} />
}
headerComponent={<SearchInput placeholder="Cari voting" />}
headerComponent={
<SearchInput placeholder="Cari voting" onChangeText={setSearch} />
}
>
{Array.from({ length: 5 }).map((_, index) => (
<Voting_BoxPublishSection key={index} href={`/voting/${index}`} />
))}
{loadingGetData ? (
<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>
);
}

View File

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

View File

@@ -1,23 +1,63 @@
/* eslint-disable react-hooks/exhaustive-deps */
import {
AlertDefaultSystem,
BackButton,
BaseBox,
DotButton,
DrawerCustom,
LoaderCustom,
MenuDrawerDynamicGrid,
Spacing,
TextCustom,
ViewWrapper,
} from "@/components";
import { IconArchive, IconContribution, IconEdit } from "@/components/_Icon";
import { IMenuDrawerItem } from "@/components/_Interface/types";
import Voting_BoxDetailHasilVotingSection from "@/screens/Voting/BoxDetailHasilVotingSection";
import { Voting_BoxDetailSection } from "@/screens/Voting/BoxDetailSection";
import Voting_ButtonStatusSection from "@/screens/Voting/ButtonStatusSection";
import { router, Stack, useLocalSearchParams } from "expo-router";
import { useState } from "react";
import {
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() {
const { id, status } = useLocalSearchParams();
const [openDrawerDraft, setOpenDrawerDraft] = 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) => {
console.log("PATH >> ", item.path);
@@ -32,9 +72,24 @@ export default function VotingDetailStatus() {
message: "Apakah Anda yakin ingin mengarsipkan voting ini?",
textLeft: "Batal",
textRight: "Ya",
onPressRight: () => {
console.log("Hapus");
router.back();
onPressRight: async () => {
try {
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
options={{
title: `Detail ${status}`,
title: `Detail`,
headerLeft: () => <BackButton />,
headerRight: () =>
status === "draft" ? (
@@ -57,9 +112,37 @@ export default function VotingDetailStatus() {
}}
/>
<ViewWrapper>
<Voting_BoxDetailSection />
<Voting_ButtonStatusSection status={status as string} />
<Spacing />
{loadingGetData ? (
<LoaderCustom />
) : (
<>
{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>
{/* ========= Draft Drawer ========= */}

View File

@@ -1,22 +1,76 @@
/* eslint-disable react-hooks/exhaustive-deps */
import {
AvatarUsernameAndOtherComponent,
BackButton,
DotButton,
DrawerCustom,
MenuDrawerDynamicGrid,
Spacing,
ViewWrapper,
AvatarUsernameAndOtherComponent,
BackButton,
DotButton,
DrawerCustom,
LoaderCustom,
MenuDrawerDynamicGrid,
Spacing,
ViewWrapper,
} from "@/components";
import { IconContribution } from "@/components/_Icon";
import { IMenuDrawerItem } from "@/components/_Interface/types";
import { useAuth } from "@/hooks/use-auth";
import { Voting_BoxDetailContributionSection } from "@/screens/Voting/BoxDetailContribution";
import Voting_BoxDetailHasilVotingSection from "@/screens/Voting/BoxDetailHasilVotingSection";
import {
apiVotingContribution,
apiVotingGetOne,
} from "@/service/api-client/api-voting";
import { router, Stack, useLocalSearchParams } from "expo-router";
import { useState } from "react";
import { useEffect, useState } from "react";
export default function VotingDetailContribution() {
const { user } = useAuth();
const { id } = useLocalSearchParams();
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) => {
router.navigate(item.path as any);
@@ -36,11 +90,27 @@ export default function VotingDetailContribution() {
/>
<ViewWrapper>
<Voting_BoxDetailContributionSection
headerAvatar={<AvatarUsernameAndOtherComponent />}
/>
<Voting_BoxDetailHasilVotingSection />
<Spacing />
{loadingGetData ? (
<LoaderCustom />
) : (
<>
<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>
{/* ========= Publish Drawer ========= */}

View File

@@ -1,29 +1,184 @@
/* eslint-disable react-hooks/exhaustive-deps */
import {
ActionIcon,
BoxButtonOnFooter,
ButtonCenteredOnly,
ButtonCustom,
Grid,
CenterCustom,
LoaderCustom,
Spacing,
StackCustom,
TextAreaCustom,
TextCustom,
TextInputCustom,
ViewWrapper,
} from "@/components";
import DateTimePickerCustom from "@/components/DateInput/DateTimePickerCustom";
import { MainColor } from "@/constants/color-palet";
import { ICON_SIZE_XLARGE } from "@/constants/constans-value";
import {
apiVotingGetOne,
apiVotingUpdateData,
} from "@/service/api-client/api-voting";
import { Ionicons } from "@expo/vector-icons";
import { router } from "expo-router";
import { TouchableOpacity } from "react-native";
import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
import _ from "lodash";
import { useCallback, useState } from "react";
import { View } from "react-native";
import Toast from "react-native-toast-message";
interface IEditData {
title?: string;
deskripsi?: string;
awalVote?: string;
akhirVote?: string;
Voting_DaftarNamaVote?: [
{
value?: string;
}
];
}
export default function VotingEdit() {
const { id } = useLocalSearchParams();
const [loadingGetData, setLoadingGetData] = useState(false);
const [data, setData] = useState<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 = () => {
return (
<>
<BoxButtonOnFooter>
<ButtonCustom
onPress={() =>
router.back()
}
isLoading={isLoading}
onPress={() => handlerUpdateSubmit()}
>
Update
</ButtonCustom>
@@ -34,45 +189,144 @@ export default function VotingEdit() {
return (
<ViewWrapper footerComponent={buttonSubmit()}>
<StackCustom gap={"xs"}>
<TextInputCustom
label="Judul Voting"
placeholder="MasukanJudul Voting"
required
/>
<TextAreaCustom
label="Deskripsi"
placeholder="Masukan Deskripsi"
required
showCount
maxLength={1000}
/>
<DateTimePickerCustom label="Mulai Voting" required />
<DateTimePickerCustom label="Voting Berakhir" required />
{loadingGetData ? (
<LoaderCustom />
) : (
<StackCustom gap={"xs"}>
<TextInputCustom
label="Judul Voting"
placeholder="MasukanJudul Voting"
required
value={data?.title}
onChangeText={(text) => setData({ ...data, title: text })}
/>
<TextAreaCustom
label="Deskripsi"
placeholder="Masukan Deskripsi"
required
showCount
maxLength={1000}
value={data?.deskripsi}
onChangeText={(text) => setData({ ...data, deskripsi: text })}
/>
<Grid>
<Grid.Col span={10}>
<Spacing />
<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
key={index}
label="Pilihan"
placeholder="Masukan Pilihan"
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")}>
Tambah Pilihan
</ButtonCenteredOnly>
<Spacing />
</StackCustom>
<CenterCustom>
<View
style={{ flexDirection: "row", alignItems: "center", gap: 10 }}
>
<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>
);
}

View File

@@ -1,23 +1,78 @@
/* eslint-disable react-hooks/exhaustive-deps */
import {
AvatarUsernameAndOtherComponent,
BackButton,
DotButton,
DrawerCustom,
MenuDrawerDynamicGrid,
Spacing,
ViewWrapper,
AvatarUsernameAndOtherComponent,
BackButton,
DotButton,
DrawerCustom,
LoaderCustom,
MenuDrawerDynamicGrid,
Spacing,
ViewWrapper,
} from "@/components";
import { IconContribution } from "@/components/_Icon";
import { IMenuDrawerItem } from "@/components/_Interface/types";
import { useAuth } from "@/hooks/use-auth";
import Voting_BoxDetailHasilVotingSection from "@/screens/Voting/BoxDetailHasilVotingSection";
import { Voting_BoxDetailHistorySection } from "@/screens/Voting/BoxDetailHistorySection";
import {
apiVotingContribution,
apiVotingGetOne,
} from "@/service/api-client/api-voting";
import { router, Stack, useLocalSearchParams } from "expo-router";
import { useState } from "react";
import { useEffect, useState } from "react";
export default function VotingDetailHistory() {
const { id } = useLocalSearchParams();
const { user } = useAuth();
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) => {
router.navigate(item.path as any);
setOpenDrawerPublish(false);
@@ -35,11 +90,27 @@ export default function VotingDetailHistory() {
}}
/>
<ViewWrapper>
<Voting_BoxDetailHistorySection
headerAvatar={<AvatarUsernameAndOtherComponent />}
/>
<Voting_BoxDetailHasilVotingSection />
<Spacing />
{loadingGetData ? (
<LoaderCustom />
) : (
<>
<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>
{/* ========= Publish Drawer ========= */}

View File

@@ -1,3 +1,4 @@
/* eslint-disable react-hooks/exhaustive-deps */
import {
AlertDefaultSystem,
AvatarUsernameAndOtherComponent,
@@ -5,20 +6,87 @@ import {
DotButton,
DrawerCustom,
InformationBox,
LoaderCustom,
MenuDrawerDynamicGrid,
StackCustom,
ViewWrapper,
} from "@/components";
import { IconArchive, IconContribution } from "@/components/_Icon";
import { IMenuDrawerItem } from "@/components/_Interface/types";
import { useAuth } from "@/hooks/use-auth";
import Voting_BoxDetailHasilVotingSection from "@/screens/Voting/BoxDetailHasilVotingSection";
import { Voting_BoxDetailPublishSection } from "@/screens/Voting/BoxDetailPublishSection";
import { router, Stack, useLocalSearchParams } from "expo-router";
import React, { useState } from "react";
import {
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() {
const { id } = useLocalSearchParams();
const { user } = useAuth();
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) => {
if (item.path === "") {
AlertDefaultSystem({
@@ -26,9 +94,24 @@ export default function VotingDetail() {
message: "Apakah Anda yakin ingin mengarsipkan voting ini?",
textLeft: "Batal",
textRight: "Ya",
onPressRight: () => {
console.log("Hapus");
router.back();
onPressRight: async () => {
try {
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>
<StackCustom>
<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." />
{loadingGetData ? (
<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
headerAvatar={<AvatarUsernameAndOtherComponent />}
/>
<Voting_BoxDetailHasilVotingSection />
</StackCustom>
<Voting_BoxDetailHasilVotingSection
listData={data?.Voting_DaftarNamaVote}
/>
</StackCustom>
)}
</ViewWrapper>
{/* ========= Publish Drawer ========= */}
@@ -67,18 +167,28 @@ export default function VotingDetail() {
height={"auto"}
>
<MenuDrawerDynamicGrid
data={[
{
icon: <IconContribution />,
label: "Daftar Kontributor",
path: `/voting/${id}/list-of-contributor`,
},
{
icon: <IconArchive />,
label: "Update Arsip",
path: "" as any,
},
]}
data={
user?.id === data?.Author?.id
? [
{
icon: <IconContribution />,
label: "Daftar Kontributor",
path: `/voting/${id}/list-of-contributor`,
},
{
icon: <IconArchive />,
label: "Update Arsip",
path: "" as any,
},
]
: [
{
icon: <IconContribution />,
label: "Daftar Kontributor",
path: `/voting/${id}/list-of-contributor`,
},
]
}
onPressItem={handlePressPublish as any}
/>
</DrawerCustom>

View File

@@ -1,26 +1,69 @@
/* eslint-disable react-hooks/exhaustive-deps */
import {
AvatarUsernameAndOtherComponent,
BadgeCustom,
BaseBox,
LoaderCustom,
TextCustom,
ViewWrapper,
} 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() {
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 (
<ViewWrapper>
{Array.from({ length: 10 }).map((_, index) => (
<BaseBox paddingTop={5} paddingBottom={5} key={index.toString()}>
<AvatarUsernameAndOtherComponent
rightComponent={
<BadgeCustom
style={{alignSelf: "flex-end" }}
>
Pilihan {index + 1}
</BadgeCustom>
}
/>
</BaseBox>
))}
{isLoadData ? (
<LoaderCustom />
) : _.isEmpty(listData) ? (
<TextCustom align="center">Tidak ada kontributor</TextCustom>
) : (
listData.map((item: any, index: number) => (
<BaseBox paddingTop={5} paddingBottom={5} key={index.toString()}>
<AvatarUsernameAndOtherComponent
avatar={item?.Author?.Profile?.imageId || ""}
name={item?.Author?.username || "Username"}
avatarHref={`/profile/${item?.Author?.Profile?.id}`}
rightComponent={
<BadgeCustom style={{ alignSelf: "flex-end" }}>
{item?.Voting_DaftarNamaVote?.value}
</BadgeCustom>
}
/>
</BaseBox>
))
)}
</ViewWrapper>
);
}

View File

@@ -1,30 +1,103 @@
import {
ActionIcon,
BoxButtonOnFooter,
ButtonCenteredOnly,
ButtonCustom,
Grid,
CenterCustom,
Spacing,
StackCustom,
TextAreaCustom,
TextInputCustom,
ViewWrapper,
ViewWrapper
} from "@/components";
import DateTimePickerCustom from "@/components/DateInput/DateTimePickerCustom";
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 { 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() {
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 = () => {
return (
<>
<BoxButtonOnFooter>
<ButtonCustom
onPress={() =>
router.replace("/(application)/(user)/voting/(tabs)/status")
}
>
<ButtonCustom isLoading={isLoading} onPress={() => handlerSubmit()}>
Simpan
</ButtonCustom>
</BoxButtonOnFooter>
@@ -39,6 +112,8 @@ export default function VotingCreate() {
label="Judul Voting"
placeholder="MasukanJudul Voting"
required
value={data.title}
onChangeText={(value: any) => setData({ ...data, title: value })}
/>
<TextAreaCustom
label="Deskripsi"
@@ -46,31 +121,81 @@ export default function VotingCreate() {
required
showCount
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}>
<TextInputCustom
label="Pilihan"
placeholder="Masukan Pilihan"
required
{listVote.map((item, index) => (
<TextInputCustom
key={index}
label="Pilihan"
placeholder="Masukan Pilihan"
required
value={item.value}
onChangeText={(value: any) =>
setListVote(
listVote.map((item, i) =>
i === index ? { ...item, value } : item
)
)
}
/>
))}
<CenterCustom>
<View style={{ flexDirection: "row", alignItems: "center", gap: 10 }}>
<ActionIcon
disabled={listVote.length >= 4}
onPress={() => {
setListVote([...listVote, { name: "Nama Pilihan", value: "" }]);
}}
icon={
<Ionicons
name="add-circle-outline"
size={ICON_SIZE_XLARGE}
color={MainColor.black}
/>
}
size="xl"
/>
</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>
<ActionIcon
disabled={listVote.length <= 2}
onPress={() => {
const list = _.clone(listVote);
list.pop();
setListVote(list);
}}
icon={
<Ionicons
name="remove-circle-outline"
size={ICON_SIZE_XLARGE}
color={MainColor.black}
/>
}
size="xl"
/>
</View>
</CenterCustom>
<Spacing />
<ButtonCenteredOnly onPress={() => console.log("add")}>
Tambah Pilihan
</ButtonCenteredOnly>
<Spacing />
</StackCustom>
</ViewWrapper>

View File

@@ -1,13 +1,13 @@
import {
AvatarCustom,
BoxButtonOnFooter,
ButtonCustom,
StackCustom,
ViewWrapper,
AvatarCustom,
BoxButtonOnFooter,
ButtonCustom,
StackCustom,
ViewWrapper,
} from "@/components";
import DIRECTORY_ID from "@/constants/directory-id";
import DUMMY_IMAGE from "@/constants/dummy-image-value";
import { uploadImageService } from "@/service/upload-service";
import { uploadFileService } from "@/service/upload-service";
import AsyncStorage from "@react-native-async-storage/async-storage";
import { router } from "expo-router";
import { useState } from "react";
@@ -21,7 +21,7 @@ export default function ScreenUpload() {
async function onUpload() {
setIsLoading(true);
try {
const response = await uploadImageService({
const response = await uploadFileService({
imageUri,
dirId: DIRECTORY_ID.profile_foto,
});

View File

@@ -23,7 +23,7 @@
"expo-constants": "~18.0.8",
"expo-dev-client": "~6.0.12",
"expo-document-picker": "~14.0.7",
"expo-file-system": "~19.0.12",
"expo-file-system": "^19.0.15",
"expo-font": "~14.0.8",
"expo-haptics": "~15.0.7",
"expo-image": "~3.0.8",
@@ -42,6 +42,7 @@
"react-native-dotenv": "^3.4.11",
"react-native-gesture-handler": "~2.28.0",
"react-native-international-phone-number": "^0.9.3",
"react-native-keyboard-controller": "^1.18.6",
"react-native-maps": "1.20.1",
"react-native-otp-entry": "^1.8.5",
"react-native-pager-view": "6.9.1",
@@ -1170,7 +1171,7 @@
"expo-document-picker": ["expo-document-picker@14.0.7", "", { "peerDependencies": { "expo": "*" } }, "sha512-81Jh8RDD0GYBUoSTmIBq30hXXjmkDV1ZY2BNIp1+3HR5PDSh2WmdhD/Ezz5YFsv46hIXHsQc+Kh1q8vn6OLT9Q=="],
"expo-file-system": ["expo-file-system@19.0.12", "", { "peerDependencies": { "expo": "*", "react-native": "*" } }, "sha512-gqpxpnjfhzXLcqMOi49isB5S1Af49P9410fsaFfnLZWN3X6Dwc8EplDwbaolOI/wnGwP81P+/nDn5RNmU6m7mQ=="],
"expo-file-system": ["expo-file-system@19.0.15", "", { "peerDependencies": { "expo": "*", "react-native": "*" } }, "sha512-sRLW+3PVJDiuoCE2LuteHhC7OxPjh1cfqLylf1YG1TDEbbQXnzwjfsKeRm6dslEPZLkMWfSLYIrVbnuq5mF7kQ=="],
"expo-font": ["expo-font@14.0.8", "", { "dependencies": { "fontfaceobserver": "^2.1.0" }, "peerDependencies": { "expo": "*", "react": "*", "react-native": "*" } }, "sha512-bTUHaJWRZ7ywP8dg3f+wfOwv6RwMV3mWT2CDUIhsK70GjNGlCtiWOCoHsA5Od/esPaVxqc37cCBvQGQRFStRlA=="],
@@ -1876,6 +1877,8 @@
"react-native-is-edge-to-edge": ["react-native-is-edge-to-edge@1.2.1", "", { "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-FLbPWl/MyYQWz+KwqOZsSyj2JmLKglHatd3xLZWskXOpRaio4LfEDEz8E/A6uD8QoTHW6Aobw1jbEwK7KMgR7Q=="],
"react-native-keyboard-controller": ["react-native-keyboard-controller@1.18.6", "", { "dependencies": { "react-native-is-edge-to-edge": "^1.2.1" }, "peerDependencies": { "react": "*", "react-native": "*", "react-native-reanimated": ">=3.0.0" } }, "sha512-K/RMw3MdtuykkACFN5d9RTapAcO0v4T34gmSyHkEraU5UsX+fxEHd6j4MvL7KUihvmLLod0NV/mQC0nL4cOurw=="],
"react-native-maps": ["react-native-maps@1.20.1", "", { "dependencies": { "@types/geojson": "^7946.0.13" }, "peerDependencies": { "react": ">= 17.0.1", "react-native": ">= 0.64.3", "react-native-web": ">= 0.11" }, "optionalPeers": ["react-native-web"] }, "sha512-NZI3B5Z6kxAb8gzb2Wxzu/+P2SlFIg1waHGIpQmazDSCRkNoHNY4g96g+xS0QPSaG/9xRBbDNnd2f2/OW6t6LQ=="],
"react-native-otp-entry": ["react-native-otp-entry@1.8.5", "", { "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-TZNkIuUzZKAAWrC8X/A22ZHJdycLysxUNysrGf0yTmDLRUyf4zLXwVFcDYUcRNe763Hjaf5qvtKGILb6lDGzoA=="],
@@ -2564,6 +2567,8 @@
"expo/babel-preset-expo": ["babel-preset-expo@54.0.0", "", { "dependencies": { "@babel/helper-module-imports": "^7.25.9", "@babel/plugin-proposal-decorators": "^7.12.9", "@babel/plugin-proposal-export-default-from": "^7.24.7", "@babel/plugin-syntax-export-default-from": "^7.24.7", "@babel/plugin-transform-class-static-block": "^7.27.1", "@babel/plugin-transform-export-namespace-from": "^7.25.9", "@babel/plugin-transform-flow-strip-types": "^7.25.2", "@babel/plugin-transform-modules-commonjs": "^7.24.8", "@babel/plugin-transform-object-rest-spread": "^7.24.7", "@babel/plugin-transform-parameters": "^7.24.7", "@babel/plugin-transform-private-methods": "^7.24.7", "@babel/plugin-transform-private-property-in-object": "^7.24.7", "@babel/plugin-transform-runtime": "^7.24.7", "@babel/preset-react": "^7.22.15", "@babel/preset-typescript": "^7.23.0", "@react-native/babel-preset": "0.81.4", "babel-plugin-react-compiler": "^19.1.0-rc.2", "babel-plugin-react-native-web": "~0.21.0", "babel-plugin-syntax-hermes-parser": "^0.25.1", "babel-plugin-transform-flow-enums": "^0.0.2", "debug": "^4.3.4", "resolve-from": "^5.0.0" }, "peerDependencies": { "@babel/runtime": "^7.20.0", "expo": "*", "react-refresh": ">=0.14.0 <1.0.0" }, "optionalPeers": ["@babel/runtime", "expo"] }, "sha512-a0Ej4ik6xzvtrA4Ivblov3XVvfntIoqnXOy2jG2k/3hzWqzrJxKyY2gUW9ZCMAicGevj2ju28q+TsK29uTe0eQ=="],
"expo/expo-file-system": ["expo-file-system@19.0.12", "", { "peerDependencies": { "expo": "*", "react-native": "*" } }, "sha512-gqpxpnjfhzXLcqMOi49isB5S1Af49P9410fsaFfnLZWN3X6Dwc8EplDwbaolOI/wnGwP81P+/nDn5RNmU6m7mQ=="],
"expo-module-scripts/typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="],
"expo-modules-autolinking/commander": ["commander@7.2.0", "", {}, "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw=="],

View File

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

View File

@@ -10,15 +10,18 @@ interface ButtonCenteredOnlyProps {
icon?: "plus" | "upload" | string;
onPress: () => void;
isLoading?: boolean;
disabled?: boolean;
}
export default function ButtonCenteredOnly({
onPress,
children,
icon = "plus",
isLoading = false,
disabled = false,
}: ButtonCenteredOnlyProps) {
return (
<ButtonCustom
disabled={disabled}
isLoading={isLoading}
onPress={onPress}
iconLeft={

View File

@@ -2,7 +2,11 @@ import DUMMY_IMAGE from "@/constants/dummy-image-value";
import { Image } from "react-native";
import BaseBox from "../Box/BaseBox";
export default function LandscapeFrameUploaded() {
export default function LandscapeFrameUploaded({
image,
}: {
image?: string;
}) {
return (
<BaseBox
style={{
@@ -11,7 +15,7 @@ export default function LandscapeFrameUploaded() {
}}
>
<Image
source={DUMMY_IMAGE.background}
source={image ? { uri: image } : DUMMY_IMAGE.dummy_image}
resizeMode="cover"
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,3 +1,4 @@
import { AccentColor } from "@/constants/color-palet";
import Divider from "../Divider/Divider";
import Grid from "../Grid/GridCustom";
import AvatarComp from "../Image/AvatarComp";
@@ -39,7 +40,7 @@ const AvatarUsernameAndOtherComponent = ({
</Grid.Col>
)}
</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 ClickableCustom from "../Clickable/ClickableCustom";
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 (
<ClickableCustom
onPress={() => {
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>
);
}

View File

@@ -0,0 +1,63 @@
// PdfViewer.tsx
import React, { useState } from "react";
import { ActivityIndicator, StyleSheet, View } from "react-native";
import WebView from "react-native-webview";
interface PdfViewerProps {
uri: string; // URL PDF dari API
}
const PdfViewer: React.FC<PdfViewerProps> = ({ uri }) => {
const [loading, setLoading] = useState(true);
return (
<>
{loading && (
<View style={styles.loadingContainer}>
<ActivityIndicator size="large" color="#007AFF" />
</View>
)}
<WebView
source={{ uri }}
style={styles.webView}
onLoadEnd={() => setLoading(false)}
onError={(syntheticEvent) => {
const { nativeEvent } = syntheticEvent;
console.warn("WebView error:", nativeEvent);
setLoading(false);
}}
scalesPageToFit={true}
javaScriptEnabled={true}
domStorageEnabled={true}
originWhitelist={["*"]}
/>
</>
);
};
// const { width, height } = Dimensions.get("window");
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "#f0f0f0",
},
loadingContainer: {
position: "absolute",
top: 0,
left: 0,
right: 0,
bottom: 0,
justifyContent: "center",
alignItems: "center",
backgroundColor: "rgba(255,255,255,0.8)",
zIndex: 1,
},
webView: {
// width: width,
// height: height,
flex: 1,
},
});
export default PdfViewer;

View File

@@ -11,6 +11,7 @@ interface SearchInputProps {
iconRight?: React.ReactNode;
containerStyle?: StyleProp<ViewStyle>;
style?: StyleProp<TextStyle>;
onChangeText?: (value: string) => void;
}
export default function SearchInput({
placeholder,
@@ -19,6 +20,7 @@ export default function SearchInput({
iconRight,
containerStyle,
style,
onChangeText,
...props
}: SearchInputProps) {
return (
@@ -30,6 +32,7 @@ export default function SearchInput({
color={MainColor.placeholder}
/>
}
onChangeText={onChangeText}
placeholder={placeholder}
borderRadius={50}
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",
value: "Rp. 7.500.000",
value: `Rp. ${formatCurrencyDisplay(data?.targetDana) || "-"}`,
},
{
label: "Harga Per Lembar",
value: "Rp. 2.400",
value: `Rp. ${formatCurrencyDisplay(data?.hargaLembar) || "-"}`,
},
{
label: "Return Of Investment (ROI)",
value: "3 %",
value: `${data?.roi || "-"} %`,
},
{
label: "Total Lembar",
value: "1.200",
},
{
label: "Jadwal Pembagian",
value: "Rp. 2.880.000",
},
{
label: "Pembagian Deviden",
value: "Selamanya",
value: data?.totalLembar || "-",
},
{
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",
value: "10",
value: data?.investor,
},
{
label: "Target Dana",
value: "Rp. 7.500.000",
value: data?.targetDana,
},
{
label: "Harga Per Lembar",
value: "Rp. 2.400",
value: data?.hargaPerLembar,
},
{
label: "Return Of Investment (ROI)",
value: "3 %",
value: data?.roi + " %",
},
{
label: "Total Lembar",
value: "1.200",
value: data?.totalLembar,
},
{
label: "Sisa Lembar",
value: "600",
value: data?.sisaLembar,
},
{
label: "Jadwal Pembagian",
value: "Rp. 2.880.000",
value: data?.jadwalPembagian,
},
{
label: "Pembagian Deviden",
value: "Selamanya",
value: data?.pembagianDeviden,
},
{
label: "Pencarian Investor",
value: "30 Hari",
value: data?.pencarianInvestor,
},
];
];

View File

@@ -30,7 +30,7 @@
"expo-constants": "~18.0.8",
"expo-dev-client": "~6.0.12",
"expo-document-picker": "~14.0.7",
"expo-file-system": "~19.0.12",
"expo-file-system": "^19.0.15",
"expo-font": "~14.0.8",
"expo-haptics": "~15.0.7",
"expo-image": "~3.0.8",
@@ -49,6 +49,7 @@
"react-native-dotenv": "^3.4.11",
"react-native-gesture-handler": "~2.28.0",
"react-native-international-phone-number": "^0.9.3",
"react-native-keyboard-controller": "^1.18.6",
"react-native-maps": "1.20.1",
"react-native-otp-entry": "^1.8.5",
"react-native-pager-view": "6.9.1",

View File

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

View File

@@ -7,24 +7,12 @@ import {
import { Href } from "expo-router";
function Collaboration_BoxPublishSection({
id,
title,
username,
description,
href,
// Avatar
sourceAvatar,
data,
rightComponentAvatar,
}: {
id: string;
title?: string;
username?: string;
description?: string;
href: Href;
// Avatar
sourceAvatar?: string;
data: any;
rightComponentAvatar?: React.ReactNode;
}) {
return (
@@ -32,21 +20,18 @@ function Collaboration_BoxPublishSection({
<BoxWithHeaderSection href={href}>
<StackCustom gap={0}>
<AvatarUsernameAndOtherComponent
avatarHref={`/profile/${id}`}
name={username || "Username"}
avatarHref={`/profile/${data?.Author?.id}`}
name={data?.Author?.username || "Username"}
rightComponent={rightComponentAvatar}
avatar={sourceAvatar as any}
avatar={data?.Author?.Profile?.imageId}
withBottomLine
/>
<StackCustom>
<TextCustom truncate={2} size="large" bold align="center">
{title || "Lorem ipsum dolor sit"}
</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 truncate size="large" bold align="center">
{data?.title || "-"}
</TextCustom>
<TextCustom truncate={2}>{data?.purpose || "-"}</TextCustom>
{/* <TextCustom bold size="small" >
2 Partisipan
</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,
setSelected,
setOpenDrawerParticipant,
listData,
}: {
selected: (string | number)[];
setSelected: (value: (string | number)[]) => void;
setOpenDrawerParticipant: (value: boolean) => void;
listData: any[];
}) {
return (
<BaseBox style={{ height: 500 }}>
@@ -31,7 +33,7 @@ export default function Collaboration_ProjectMainSelectedSection({
</TextCustom>
<CheckboxGroup value={selected} onChange={setSelected}>
{Array.from({ length: 5 }).map((_, index) => (
{listData?.map((item: any, index: any) => (
<View key={index}>
<Grid key={index}>
<Grid.Col

View File

@@ -127,7 +127,7 @@ export default function Event_ButtonStatusSection({
if (response.success) {
Toast.show({
type: "success",
text1: response.message,
text1: "Data dihapus",
});
router.back();
} else {

View File

@@ -1,30 +1,44 @@
import {
AvatarCustom,
BaseBox,
AvatarComp,
BoxWithHeaderSection,
ClickableCustom,
Grid,
Spacing,
TextCustom,
TextCustom
} from "@/components";
import { MainColor } from "@/constants/color-palet";
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 { View } from "react-native";
export default function Forum_CommentarBoxSection({
data,
setOpenDrawer,
onSetData,
}: {
data: any;
setOpenDrawer: (value: boolean) => void;
onSetData: ({
setCommentId,
setOpenDrawer,
setCommentAuthorId,
}: {
setCommentId: string;
setOpenDrawer: boolean;
setCommentAuthorId: string;
}) => void;
}) {
return (
<>
<BaseBox>
<BoxWithHeaderSection>
<View>
<Grid>
<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
span={8}
@@ -32,7 +46,7 @@ export default function Forum_CommentarBoxSection({
justifyContent: "center",
}}
>
<TextCustom>{data.name}</TextCustom>
<TextCustom>{data?.Author?.username}</TextCustom>
</Grid.Col>
<Grid.Col
@@ -43,7 +57,11 @@ export default function Forum_CommentarBoxSection({
>
<ClickableCustom
onPress={() => {
setOpenDrawer(true);
onSetData({
setCommentId: data?.id,
setOpenDrawer: true,
setCommentAuthorId: data?.Author?.id,
});
}}
style={{
alignItems: "flex-end",
@@ -58,14 +76,18 @@ export default function Forum_CommentarBoxSection({
</Grid.Col>
</Grid>
<TextCustom>{data.deskripsi}</TextCustom>
<View style={GStyles.forumBox}>
<TextCustom>{data.komentar}</TextCustom>
</View>
<Spacing />
<Spacing height={10} />
<View style={{ alignItems: "flex-end" }}>
<TextCustom>{data.date}</TextCustom>
<TextCustom size="small" color="gray">
{formatChatTime(data?.createdAt)}
</TextCustom>
</View>
</View>
</BaseBox>
</BoxWithHeaderSection>
</>
);
}

View File

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

View File

@@ -2,24 +2,20 @@ import { MainColor } from "@/constants/color-palet";
import { ICON_SIZE_SMALL } from "@/constants/constans-value";
import { Feather, Ionicons } from "@expo/vector-icons";
export { drawerItemsForumBeranda, drawerItemsForumComentar };
export {
drawerItemsForumBerandaForAuthor,
drawerItemsForumComentarForAuthor,
drawerItemsForumBerandaForNonAuthor,
drawerItemsForumComentarForNonAuthor,
};
const drawerItemsForumBeranda = ({
const drawerItemsForumBerandaForAuthor = ({
id,
status,
}: {
id: 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: (
<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: (
<Ionicons name="flag" size={ICON_SIZE_SMALL} color={MainColor.white} />
),
label: "Laporkan",
label: "Laporkan diskusi",
// color: MainColor.white,
path: `/forum/${id}/report-commentar`,
path: `/forum/${id}/report-posting`,
},
];
const drawerItemsForumComentarForAuthor = ({ id }: { id: string }) => [
{
icon: (
<Ionicons name="trash" size={ICON_SIZE_SMALL} color={MainColor.white} />
@@ -67,3 +66,14 @@ const drawerItemsForumComentar = ({ id }: { id: string }) => [
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 MenuDrawerDynamicGrid from "@/components/Drawer/MenuDrawerDynamicGird";
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({
id,
status,
setIsDrawerOpen,
setShowDeleteAlert,
setShowAlertStatus,
authorId,
handlerUpdateStatus,
}: {
id: string;
status: string;
setIsDrawerOpen: (value: boolean) => void;
setShowDeleteAlert: (value: boolean) => void;
setShowAlertStatus: (value: boolean) => void;
authorId: string;
handlerUpdateStatus?: (value: string) => void;
}) {
const { user } = useAuth();
const handlePress = (item: IMenuDrawerItem) => {
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") {
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 {
router.push(item.path as any);
}
@@ -32,9 +73,13 @@ export default function Forum_MenuDrawerBerandaSection({
<>
{/* Menu Items */}
<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
onPressItem={handlePress}
onPressItem={handlePress as any}
/>
</>
);

View File

@@ -1,19 +1,67 @@
import { MenuDrawerDynamicGrid } from "@/components";
import { drawerItemsForumComentar } from "../ListPage";
import { AlertDefaultSystem, MenuDrawerDynamicGrid } from "@/components";
import { useAuth } from "@/hooks/use-auth";
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({
id,
setShowDeleteAlert,
setIsDrawerOpen,
commentId,
commentAuthorId,
listComment,
setListComment,
countComment,
setCountComment,
}: {
id: string;
setShowDeleteAlert: (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) => {
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 {
router.push(item.path as any);
}
@@ -24,7 +72,11 @@ export default function Forum_MenuDrawerCommentar({
return (
<>
<MenuDrawerDynamicGrid
data={drawerItemsForumComentar({ id })}
data={
commentAuthorId === user?.id
? drawerItemsForumComentarForAuthor({ id })
: drawerItemsForumComentarForNonAuthor({ id })
}
columns={4}
onPressItem={handlePress}
/>

View File

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

View File

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

View File

@@ -1,22 +1,54 @@
import { AlertDefaultSystem, ButtonCustom, Grid } from "@/components";
import {
apiInvestmentDelete,
apiInvestmentUpdateStatus,
} from "@/service/api-client/api-investment";
import { deleteFileService } from "@/service/upload-service";
import { router } from "expo-router";
import { useState } from "react";
import Toast from "react-native-toast-message";
export default function Investment_ButtonStatusSection({
id,
status,
buttonPublish
buttonPublish,
}: {
id: string;
status: string;
buttonPublish?: React.ReactNode;
}) {
const [isLoading, setIsLoading] = useState(false);
const handleBatalkanReview = () => {
AlertDefaultSystem({
title: "Batalkan Review",
message: "Apakah Anda yakin ingin batalkan review ini?",
textLeft: "Batal",
textRight: "Ya",
onPressRight: () => {
console.log("Hapus");
router.back();
onPressRight: async () => {
try {
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?",
textLeft: "Batal",
textRight: "Ya",
onPressRight: () => {
console.log("Hapus");
router.back();
onPressRight: async () => {
try {
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?",
textLeft: "Batal",
textRight: "Ya",
onPressRight: () => {
console.log("Hapus");
router.back();
onPressRight: async () => {
try {
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?",
textLeft: "Batal",
textRight: "Hapus",
onPressRight: () => {
console.log("Hapus");
router.back();
onPressRight: async () => {
try {
setIsLoading(true);
const response = await apiInvestmentDelete({
id: id as string,
});
console.log("[RESPONSE]", JSON.stringify(response, null, 2));
if (response.success) {
const deleteImage = await deleteFileService({
id: response?.data?.imageId as string,
});
if (!deleteImage.success) {
Toast.show({
type: "error",
text1: "Gagal Hapus Data",
});
return;
}
const deleteFile = await deleteFileService({
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 (
<>
<ButtonCustom
isLoading={isLoading}
backgroundColor="red"
textColor="white"
onPress={handleOpenDeleteAlert}
@@ -76,13 +199,11 @@ export default function Investment_ButtonStatusSection({
switch (status) {
case "publish":
return <>
{buttonPublish}
</>;
return <>{buttonPublish}</>;
case "review":
return (
<ButtonCustom onPress={handleBatalkanReview}>
<ButtonCustom isLoading={isLoading} onPress={handleBatalkanReview}>
Batalkan Review
</ButtonCustom>
);
@@ -92,7 +213,7 @@ export default function Investment_ButtonStatusSection({
<>
<Grid>
<Grid.Col span={6} style={{ paddingRight: 10 }}>
<ButtonCustom onPress={handleAjukanReview}>
<ButtonCustom isLoading={isLoading} onPress={handleAjukanReview}>
Ajukan Review
</ButtonCustom>
</Grid.Col>
@@ -108,7 +229,7 @@ export default function Investment_ButtonStatusSection({
<>
<Grid>
<Grid.Col span={6} style={{ paddingRight: 10 }}>
<ButtonCustom onPress={handleEditKembali}>
<ButtonCustom isLoading={isLoading} onPress={handleEditKembali}>
Edit Kembali
</ButtonCustom>
</Grid.Col>

View File

@@ -11,11 +11,14 @@ import { Ionicons } from "@expo/vector-icons";
export default function Invesment_ComponentBoxOnBottomDetail({
id,
prospectusId,
status,
}: {
id: string;
prospectusId: string;
status: string;
}) {
return (
<>
{status === "publish" ? (
@@ -94,7 +97,7 @@ export default function Invesment_ComponentBoxOnBottomDetail({
<BaseBox
backgroundColor={AccentColor.blue}
style={{ borderColor: AccentColor.softblue, borderWidth: 1 }}
href={`/(file)/${id}`}
href={`/(file)/${prospectusId}`}
>
<StackCustom>
<TextCustom align="center">Prospektus</TextCustom>

View File

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

View File

@@ -1,44 +1,43 @@
import { BaseBox, Grid, Spacing, TextCustom } from "@/components";
import API_IMAGE from "@/constants/api-storage";
import DUMMY_IMAGE from "@/constants/dummy-image-value";
import { formatCurrencyDisplay } from "@/utils/formatCurrencyDisplay";
import { Image } from "expo-image";
import { Href } from "expo-router";
import { View } from "react-native";
interface Investment_StatusBoxProps {
id: string;
data: any;
status: string;
href?: Href
href?: Href;
}
export default function Investment_StatusBox({
id,
data,
status,
href
href,
}: Investment_StatusBoxProps) {
return (
<BaseBox paddingTop={7} paddingBottom={7} href={href}>
<Grid>
<Grid.Col span={6}>
<TextCustom truncate={2}>
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>
<TextCustom truncate={2}>{data?.title || ""}</TextCustom>
<Spacing />
<TextCustom bold size="small">
Target Dana:
</TextCustom>
<TextCustom>Rp. 7.500.000</TextCustom>
<TextCustom truncate>
Rp. {formatCurrencyDisplay(data?.targetDana) || ""}
</TextCustom>
</Grid.Col>
<Grid.Col span={1}>
<View />
</Grid.Col>
<Grid.Col span={5}>
<Image
source={DUMMY_IMAGE.background}
source={API_IMAGE.GET({ fileId: data?.imageId })}
style={{ width: "auto", height: 100, borderRadius: 10 }}
/>
</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}) {
return (
<>
<BaseBox>
<StackCustom gap={"lg"}>
<DummyLandscapeImage />
export default function Job_BoxDetailSection({ data }: { data: any }) {
return (
<>
<BaseBox>
<StackCustom gap={"lg"}>
{data && data.imageId && (
<DummyLandscapeImage imageId={data?.imageId} />
)}
<TextCustom align="center" bold size="large">
{data?.posisi}
</TextCustom>
<TextCustom align="center" bold size="large">
{data?.title || "-"}
</TextCustom>
<StackCustom gap={"sm"}>
<TextCustom bold>Syarat & Ketentuan :</TextCustom>
<TextCustom>{data?.syaratKetentuan}</TextCustom>
</StackCustom>
<StackCustom gap={"sm"}>
<TextCustom bold>Deskripsi :</TextCustom>
<TextCustom>{data?.deskripsi}</TextCustom>
</StackCustom>
<StackCustom gap={"sm"}>
<TextCustom bold>Syarat & Ketentuan :</TextCustom>
<TextCustom>{data?.content || "-"}</TextCustom>
</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 {
apiJobDelete,
apiJobUpdateData,
apiJobUpdateStatus,
} from "@/service/api-client/api-job";
import { router } from "expo-router";
import { View } from "react-native";
import Toast from "react-native-toast-message";
export default function Job_ButtonStatusSection({
id,
status,
isLoading,
onSetLoading,
isArchive,
}: {
id: string;
status: string;
isLoading: boolean;
onSetLoading: (value: boolean) => void;
isArchive?: boolean;
}) {
const handleBatalkanReview = () => {
AlertDefaultSystem({
@@ -13,9 +26,33 @@ export default function Job_ButtonStatusSection({
message: "Apakah Anda yakin ingin batalkan review ini?",
textLeft: "Batal",
textRight: "Ya",
onPressRight: () => {
console.log("Hapus");
router.back();
onPressRight: async () => {
try {
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?",
textLeft: "Batal",
textRight: "Ya",
onPressRight: () => {
console.log("Hapus");
router.back();
onPressRight: async () => {
try {
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?",
textLeft: "Batal",
textRight: "Ya",
onPressRight: () => {
console.log("Hapus");
router.back();
onPressRight: async () => {
try {
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?",
textLeft: "Batal",
textRight: "Hapus",
onPressRight: () => {
console.log("Hapus");
router.back();
onPressRight: async () => {
try {
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 (
<>
<ButtonCustom
isLoading={isLoading}
backgroundColor="red"
textColor="white"
onPress={handleOpenDeleteAlert}
@@ -78,9 +226,9 @@ export default function Job_ButtonStatusSection({
return (
<>
<ButtonCustom
isLoading={isLoading}
onPress={() => {
console.log("Arsipkan");
router.replace("/(application)/(user)/job/(tabs)/archive");
handleArchive();
}}
>
Arsipkan
@@ -90,7 +238,7 @@ export default function Job_ButtonStatusSection({
case "review":
return (
<ButtonCustom onPress={handleBatalkanReview}>
<ButtonCustom isLoading={isLoading} onPress={handleBatalkanReview}>
Batalkan Review
</ButtonCustom>
);
@@ -99,15 +247,14 @@ export default function Job_ButtonStatusSection({
return (
<>
<Grid>
<Grid.Col span={5}>
<ButtonCustom onPress={handleAjukanReview}>
<Grid.Col span={6} style={{ paddingRight: 10 }}>
<ButtonCustom isLoading={isLoading} onPress={handleAjukanReview}>
Ajukan Review
</ButtonCustom>
</Grid.Col>
<Grid.Col span={2}>
<View />
<Grid.Col span={6} style={{ paddingLeft: 10 }}>
{DeleteButton()}
</Grid.Col>
<Grid.Col span={5}>{DeleteButton()}</Grid.Col>
</Grid>
</>
);
@@ -116,15 +263,15 @@ export default function Job_ButtonStatusSection({
return (
<>
<Grid>
<Grid.Col span={5}>
<ButtonCustom onPress={handleEditKembali}>
<Grid.Col span={6} style={{ paddingRight: 10 }}>
<ButtonCustom isLoading={isLoading} onPress={handleEditKembali}>
Edit Kembali
</ButtonCustom>
</Grid.Col>
<Grid.Col span={2}>
<View />
<Grid.Col span={6} style={{ paddingRight: 10 }}>
{DeleteButton()}
</Grid.Col>
<Grid.Col span={5}>{DeleteButton()}</Grid.Col>
</Grid>
</>
);

View File

@@ -1,7 +1,7 @@
import { BoxButtonOnFooter, ButtonCustom } from "@/components";
import DIRECTORY_ID from "@/constants/directory-id";
import { apiPortofolioCreate } from "@/service/api-client/api-portofolio";
import { uploadImageService } from "@/service/upload-service";
import { uploadFileService } from "@/service/upload-service";
import { router } from "expo-router";
import Toast from "react-native-toast-message";
@@ -72,7 +72,7 @@ export default function Portofolio_ButtonCreate({
setIsLoadingCreate(true);
let fileId = "";
if (imageUri) {
const response = await uploadImageService({
const response = await uploadFileService({
imageUri: imageUri,
dirId: DIRECTORY_ID.portofolio_logo,
});

View File

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

View File

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

View File

@@ -1,21 +1,36 @@
import {
BadgeCustom,
BoxWithHeaderSection,
Spacing,
StackCustom
StackCustom,
TextCustom
} from "@/components";
import { Voting_ComponentDetailDataSection } from "./ComponentDetailDataSection";
import { GStyles } from "@/styles/global-styles";
export function Voting_BoxDetailHistorySection({
headerAvatar,
data,
nameChoice,
}: {
headerAvatar?: React.ReactNode;
data: any;
nameChoice: string;
}) {
return (
<>
<BoxWithHeaderSection>
{headerAvatar ? headerAvatar : <Spacing />}
<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>
</BoxWithHeaderSection>
</>

View File

@@ -1,4 +1,6 @@
import {
AlertDefaultSystem,
BadgeCustom,
BoxWithHeaderSection,
ButtonCustom,
Spacing,
@@ -6,40 +8,109 @@ import {
TextCustom
} from "@/components";
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 { View } from "react-native";
import { Voting_ComponentDetailDataSection } from "./ComponentDetailDataSection";
export function Voting_BoxDetailPublishSection({
headerAvatar,
data,
userId,
isContribution,
nameChoice,
}: {
headerAvatar?: React.ReactNode;
data?: any;
userId: string;
isContribution?: boolean;
nameChoice?: string;
}) {
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 (
<>
<BoxWithHeaderSection>
{headerAvatar ? headerAvatar : <Spacing />}
{headerAvatar && (
<>
{headerAvatar}
<Spacing />
</>
)}
<StackCustom gap={"lg"}>
<Voting_ComponentDetailDataSection />
<Voting_ComponentDetailDataSection data={data} />
<View>
<TextCustom bold size="small">
Pilihan :
</TextCustom>
<RadioGroup value={value} onChange={setValue}>
{Array.from({ length: 4 }).map((_, i) => (
<View key={i}>
<RadioCustom
label={`Pilihan ${i + 1}`}
value={`Pilihan ${i + 1}`}
/>
</View>
))}
</RadioGroup>
</View>
{isContribution ? (
<StackCustom gap={"sm"}>
<TextCustom align="center" size="small" bold>
Pilihan Anda :
</TextCustom>
<View style={{ alignSelf: "center" }}>
<BadgeCustom variant="light" size="lg">
{nameChoice || "-"}
</BadgeCustom>
</View>
</StackCustom>
) : (
<>
<StackCustom>
<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>
</BoxWithHeaderSection>
</>

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,151 @@
import { apiConfig } from "../api-config";
export async function apiCollaborationCreate({ data }: { data: any }) {
try {
const response = await apiConfig.post(`/mobile/collaboration`, {
data: data,
});
return response.data;
} catch (error) {
throw error;
}
}
export async function apiCollaborationGetAll({
category,
authorId,
}: {
category: "beranda" | "participant" | "my-project" | "group";
authorId?: string;
}) {
try {
const authorQuery = authorId ? `&authorId=${authorId}` : "";
const response = await apiConfig.get(
`/mobile/collaboration?category=${category}${authorQuery}`
);
return response.data;
} catch (error) {
throw error;
}
}
export async function apiCollaborationGetOne({ id }: { id: string }) {
try {
const response = await apiConfig.get(`/mobile/collaboration/${id}`);
return response.data;
} catch (error) {
throw error;
}
}
export async function apiCollaborationCreatePartisipasi({
id,
data,
}: {
id: string;
data: any;
}) {
try {
const response = await apiConfig.post(
`/mobile/collaboration/${id}/participants`,
{
data: data,
}
);
return response.data;
} catch (error) {
throw error;
}
}
export async function apiCollaborationGetParticipants({
id,
category,
authorId,
}: {
id: string;
category: "list" | "check-participant";
authorId?: string;
}) {
try {
const authorQuery = authorId ? `&authorId=${authorId}` : "";
const response = await apiConfig.get(
`/mobile/collaboration/${id}/participants?category=${category}${authorQuery}`
);
return response.data;
} catch (error) {
throw error;
}
}
export async function apiCollaborationCreateGroup({
id,
data,
}: {
id: string;
data: any;
}) {
try {
const response = await apiConfig.post(`/mobile/collaboration/${id}`, {
data: data,
});
return response.data;
} catch (error) {
throw error;
}
}
export async function apiCollaborationEditData({
id,
data,
}: {
id: string;
data: any;
}) {
try {
const response = await apiConfig.put(`/mobile/collaboration/${id}`, {
data: data,
});
return response.data;
} catch (error) {
throw error;
}
}
export async function apiCollaborationGroup({ id }: { id: string }) {
try {
const response = await apiConfig.get(`/mobile/collaboration/${id}/group`);
return response.data;
} catch (error) {
throw error;
}
}
export async function apiCollaborationGroupMessage({ id }: { id: string }) {
try {
const response = await apiConfig.get(`/mobile/collaboration/${id}/message`);
return response.data;
} catch (error) {
throw error;
}
}
export async function apiCollaborationGroupMessageCreate({
id,
data,
}: {
id: string;
data: any;
}) {
try {
const response = await apiConfig.post(
`/mobile/collaboration/${id}/message`,
{
data: data,
}
);
return response.data;
} catch (error) {
throw error;
}
}

View File

@@ -0,0 +1,112 @@
import { apiConfig } from "../api-config";
export async function apiForumCreate({ data }: { data: any }) {
try {
const response = await apiConfig.post(`/mobile/forum`, {
data: data,
});
return response.data;
} catch (error) {
throw error;
}
}
export async function apiForumGetAll({
search,
authorId,
}: {
search: string;
authorId?: string;
}) {
const authorQuery = authorId ? `?authorId=${authorId}` : "";
const searchQuery = search ? `?search=${search}` : "";
const query = search ? searchQuery : authorQuery;
try {
const response = await apiConfig.get(`/mobile/forum${query}`);
return response.data;
} catch (error) {
throw error;
}
}
export async function apiForumGetOne({ id }: { id: string }) {
try {
const response = await apiConfig.get(`/mobile/forum/${id}`);
return response.data;
} catch (error) {
throw error;
}
}
export async function apiForumUpdate({ id, data }: { id: string; data: any }) {
try {
const response = await apiConfig.put(`/mobile/forum/${id}`, {
data: data,
});
return response.data;
} catch (error) {
throw error;
}
}
export async function apiForumUpdateStatus({
id,
data,
}: {
id: string;
data: any;
}) {
try {
const response = await apiConfig.post(`/mobile/forum/${id}`, {
data: data,
});
return response.data;
} catch (error) {
throw error;
}
}
export async function apiForumDelete({ id }: { id: string }) {
try {
const response = await apiConfig.delete(`/mobile/forum/${id}`);
return response.data;
} catch (error) {
throw error;
}
}
export async function apiForumCreateComment({
id,
data,
}: {
id: string;
data: any;
}) {
try {
const response = await apiConfig.post(`/mobile/forum/${id}/comment`, {
data: data,
});
return response.data;
} catch (error) {
throw error;
}
}
export async function apiForumGetComment({ id }: { id: string }) {
try {
const response = await apiConfig.get(`/mobile/forum/${id}/comment`);
return response.data;
} catch (error) {
throw error;
}
}
export async function apiForumDeleteComment({ id }: { id: string }) {
try {
const response = await apiConfig.delete(`/mobile/forum/${id}/comment`);
return response.data;
} catch (error) {
throw error;
}
}

View File

@@ -0,0 +1,83 @@
import { apiConfig } from "../api-config";
export async function apiInvestmentCreate({ data }: { data: any }) {
try {
const response = await apiConfig.post(`/mobile/investment`, {
data: data,
});
return response.data;
} catch (error) {
throw error;
}
}
export async function apiInvestmentGetByStatus({
authorId,
status,
}: {
authorId: string;
status: string;
}) {
try {
const response = await apiConfig.get(
`/mobile/investment/${authorId}/${status}`
);
return response.data;
} catch (error) {
throw error;
}
}
export async function apiInvestmentGetById({ id }: { id: string }) {
try {
const response = await apiConfig.get(`/mobile/investment/${id}`);
return response.data;
} catch (error) {
throw error;
}
}
export async function apiInvestmentUpdateStatus({
id,
status,
}: {
id: string;
status: "publish" | "draft" | "review" | "reject";
}) {
try {
const response = await apiConfig.put(`/mobile/investment/${id}/${status}`);
return response.data;
} catch (error) {
throw error;
}
}
export async function apiInvestmentDelete({ id }: { id: string }) {
try {
const response = await apiConfig.delete(`/mobile/investment/${id}`);
return response.data;
} catch (error) {
throw error;
}
}
export async function apiInvestmentUpdateData({
id,
data,
category,
}: {
id: string;
data: any;
category: "data" | "prospectus";
}) {
try {
const response = await apiConfig.put(`/mobile/investment/${id}?category=${category}`, {
data: data,
});
return response.data;
} catch (error) {
throw error;
}
}

View File

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

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