Investment
Add: - components/Button/CoyButton.tsx - constants/local-storage-key.ts Fix: - Integrasi pada proses transaksi pmebelian investasi ### No Issue
This commit is contained in:
@@ -1,81 +1,123 @@
|
|||||||
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
import {
|
import {
|
||||||
BadgeCustom,
|
BadgeCustom,
|
||||||
BaseBox,
|
BaseBox,
|
||||||
Grid,
|
Grid,
|
||||||
|
LoaderCustom,
|
||||||
StackCustom,
|
StackCustom,
|
||||||
TextCustom,
|
TextCustom,
|
||||||
ViewWrapper,
|
ViewWrapper,
|
||||||
} from "@/components";
|
} from "@/components";
|
||||||
import { dummyMasterStatusTransaction } from "@/lib/dummy-data/_master/status-transaction";
|
import { useAuth } from "@/hooks/use-auth";
|
||||||
|
import { apiInvestmentGetInvoice } from "@/service/api-client/api-investment";
|
||||||
import { GStyles } from "@/styles/global-styles";
|
import { GStyles } from "@/styles/global-styles";
|
||||||
import dayjs from "dayjs";
|
import { formatChatTime } from "@/utils/formatChatTime";
|
||||||
import { router } from "expo-router";
|
import { formatCurrencyDisplay } from "@/utils/formatCurrencyDisplay";
|
||||||
|
import { router, useFocusEffect } from "expo-router";
|
||||||
|
import _ from "lodash";
|
||||||
|
import { useCallback, useState } from "react";
|
||||||
import { View } from "react-native";
|
import { View } from "react-native";
|
||||||
|
|
||||||
export default function InvestmentTransaction() {
|
export default function InvestmentTransaction() {
|
||||||
const randomStatusData = Array.from({ length: 10 }, () => {
|
const { user } = useAuth();
|
||||||
const randomIndex = Math.floor(
|
const [list, setList] = useState<any>([]);
|
||||||
Math.random() * dummyMasterStatusTransaction.length
|
const [loadList, setLoadList] = useState<boolean>(false);
|
||||||
);
|
|
||||||
return dummyMasterStatusTransaction[randomIndex];
|
|
||||||
});
|
|
||||||
|
|
||||||
const handlePress = (value: string) => {
|
useFocusEffect(
|
||||||
if (value === "menunggu") {
|
useCallback(() => {
|
||||||
router.push(`/investment/${value}/(transaction-flow)/invoice`);
|
onLoadList();
|
||||||
} else if (value === "proses") {
|
}, [user?.id])
|
||||||
router.push(`/investment/${value}/(transaction-flow)/process`);
|
);
|
||||||
} else if (value === "berhasil") {
|
|
||||||
router.push(`/investment/${value}/(transaction-flow)/success`);
|
const onLoadList = async () => {
|
||||||
} else if (value === "gagal") {
|
try {
|
||||||
router.push(`/investment/${value}/(transaction-flow)/failed`);
|
setLoadList(true);
|
||||||
|
const response = await apiInvestmentGetInvoice({
|
||||||
|
authorId: user?.id as string,
|
||||||
|
category: "transaction",
|
||||||
|
});
|
||||||
|
console.log("[RESPONSE LIST]", JSON.stringify(response.data, null, 2));
|
||||||
|
setList(response.data);
|
||||||
|
} catch (error) {
|
||||||
|
console.log("[ERROR]", error);
|
||||||
|
} finally {
|
||||||
|
setLoadList(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handlerColor = (status: string) => {
|
||||||
|
if (status === "menunggu") {
|
||||||
|
return "orange";
|
||||||
|
} else if (status === "proses") {
|
||||||
|
return "white";
|
||||||
|
} else if (status === "berhasil") {
|
||||||
|
return "green";
|
||||||
|
} else if (status === "gagal") {
|
||||||
|
return "red";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handlePress = ({ id, status }: { id: string; status: string }) => {
|
||||||
|
if (status === "menunggu") {
|
||||||
|
router.push(`/investment/${id}/(transaction-flow)/invoice`);
|
||||||
|
} else if (status === "proses") {
|
||||||
|
router.push(`/investment/${id}/(transaction-flow)/process`);
|
||||||
|
} else if (status === "berhasil") {
|
||||||
|
router.push(`/investment/${id}/(transaction-flow)/success`);
|
||||||
|
} else if (status === "gagal") {
|
||||||
|
router.push(`/investment/${id}/(transaction-flow)/failed`);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ViewWrapper hideFooter>
|
<ViewWrapper hideFooter>
|
||||||
{randomStatusData.map((item, i) => (
|
{loadList ? (
|
||||||
<BaseBox
|
<LoaderCustom />
|
||||||
key={i}
|
) : _.isEmpty(list) ? (
|
||||||
paddingTop={7}
|
<TextCustom>Tidak ada data</TextCustom>
|
||||||
paddingBottom={7}
|
) : (
|
||||||
onPress={() => {
|
list.map((item: any, i: number) => (
|
||||||
handlePress(item.value);
|
<BaseBox
|
||||||
}}
|
key={i}
|
||||||
>
|
paddingTop={7}
|
||||||
<Grid>
|
paddingBottom={7}
|
||||||
<Grid.Col span={6}>
|
onPress={() => {
|
||||||
<StackCustom gap={"xs"}>
|
handlePress({
|
||||||
<TextCustom truncate>
|
id: item.id,
|
||||||
Title Investment: Lorem ipsum dolor sit amet consectetur
|
status: _.lowerCase(item.statusInvoice),
|
||||||
adipisicing elit. Am culpa excepturi deleniti soluta animi
|
});
|
||||||
porro amet ducimus.
|
}}
|
||||||
</TextCustom>
|
>
|
||||||
<TextCustom color="gray" size="small">
|
<Grid>
|
||||||
{dayjs().format("DD/MM/YYYY")}
|
<Grid.Col span={6}>
|
||||||
</TextCustom>
|
<StackCustom gap={"xs"}>
|
||||||
</StackCustom>
|
<TextCustom truncate>{item?.title || "-"}</TextCustom>
|
||||||
</Grid.Col>
|
<TextCustom color="gray" size="small">
|
||||||
<Grid.Col span={1}>
|
{formatChatTime(item?.createdAt)}
|
||||||
<View />
|
</TextCustom>
|
||||||
</Grid.Col>
|
</StackCustom>
|
||||||
<Grid.Col span={5} style={{ alignItems: "flex-end" }}>
|
</Grid.Col>
|
||||||
<StackCustom gap={"xs"}>
|
<Grid.Col span={1}>
|
||||||
<TextCustom bold truncate>
|
<View />
|
||||||
Rp. 7.500.000
|
</Grid.Col>
|
||||||
</TextCustom>
|
<Grid.Col span={5} style={{ alignItems: "flex-end" }}>
|
||||||
<BadgeCustom
|
<StackCustom gap={"xs"}>
|
||||||
variant="light"
|
<TextCustom bold truncate>
|
||||||
color={item.color}
|
Rp. {formatCurrencyDisplay(item?.nominal) || "-"}
|
||||||
style={GStyles.alignSelfFlexEnd}
|
</TextCustom>
|
||||||
>
|
<BadgeCustom
|
||||||
{item.label}
|
variant="light"
|
||||||
</BadgeCustom>
|
color={handlerColor(_.lowerCase(item.statusInvoice))}
|
||||||
</StackCustom>
|
style={GStyles.alignSelfFlexEnd}
|
||||||
</Grid.Col>
|
>
|
||||||
</Grid>
|
{item?.statusInvoice || "-"}
|
||||||
</BaseBox>
|
</BadgeCustom>
|
||||||
))}
|
</StackCustom>
|
||||||
|
</Grid.Col>
|
||||||
|
</Grid>
|
||||||
|
</BaseBox>
|
||||||
|
))
|
||||||
|
)}
|
||||||
</ViewWrapper>
|
</ViewWrapper>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,8 +10,10 @@ import {
|
|||||||
TextInputCustom,
|
TextInputCustom,
|
||||||
ViewWrapper,
|
ViewWrapper,
|
||||||
} from "@/components";
|
} from "@/components";
|
||||||
|
import { LOCAL_STORAGE_KEY } from "@/constants/local-storage-key";
|
||||||
import { apiInvestmentGetOne } from "@/service/api-client/api-investment";
|
import { apiInvestmentGetOne } from "@/service/api-client/api-investment";
|
||||||
import { formatCurrencyDisplay } from "@/utils/formatCurrencyDisplay";
|
import { formatCurrencyDisplay } from "@/utils/formatCurrencyDisplay";
|
||||||
|
import AsyncStorage from "@react-native-async-storage/async-storage";
|
||||||
import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
|
import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
|
||||||
import { useCallback, useState } from "react";
|
import { useCallback, useState } from "react";
|
||||||
|
|
||||||
@@ -19,9 +21,10 @@ export default function InvestmentInvest() {
|
|||||||
const { id } = useLocalSearchParams();
|
const { id } = useLocalSearchParams();
|
||||||
// console.log("[ID]", id);
|
// console.log("[ID]", id);
|
||||||
const [data, setData] = useState<any>(null);
|
const [data, setData] = useState<any>(null);
|
||||||
const [value, setValue] = useState<number>(0);
|
const [jumlah, setJumlah] = useState<number>(0);
|
||||||
const [total, setTotal] = useState<number>(0);
|
const [total, setTotal] = useState<number>(0);
|
||||||
const [sisaLembar, setSisaLembar] = useState<number>(0);
|
const [sisaLembar, setSisaLembar] = useState<number>(0);
|
||||||
|
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||||
|
|
||||||
useFocusEffect(
|
useFocusEffect(
|
||||||
useCallback(() => {
|
useCallback(() => {
|
||||||
@@ -45,7 +48,7 @@ export default function InvestmentInvest() {
|
|||||||
const handleTextChange = (text: string) => {
|
const handleTextChange = (text: string) => {
|
||||||
// Izinkan input kosong → anggap sebagai 0 (atau abaikan, tergantung UX)
|
// Izinkan input kosong → anggap sebagai 0 (atau abaikan, tergantung UX)
|
||||||
if (text === "") {
|
if (text === "") {
|
||||||
setValue(0);
|
setJumlah(0);
|
||||||
setTotal(0);
|
setTotal(0);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -58,7 +61,7 @@ export default function InvestmentInvest() {
|
|||||||
// Karena regex sudah pastikan hanya angka, isNaN biasanya false
|
// Karena regex sudah pastikan hanya angka, isNaN biasanya false
|
||||||
// Tapi tetap aman untuk cek
|
// Tapi tetap aman untuk cek
|
||||||
if (!isNaN(numValue)) {
|
if (!isNaN(numValue)) {
|
||||||
setValue(numValue);
|
setJumlah(numValue);
|
||||||
setTotal(numValue * Number(data?.hargaLembar));
|
setTotal(numValue * Number(data?.hargaLembar));
|
||||||
console.log("[VALUE]", numValue);
|
console.log("[VALUE]", numValue);
|
||||||
}
|
}
|
||||||
@@ -71,10 +74,24 @@ export default function InvestmentInvest() {
|
|||||||
<>
|
<>
|
||||||
<BoxButtonOnFooter>
|
<BoxButtonOnFooter>
|
||||||
<ButtonCustom
|
<ButtonCustom
|
||||||
disabled={value < 10 || value >= sisaLembar}
|
isLoading={isLoading}
|
||||||
onPress={() => router.push(`/investment/${id}/select-bank`)}
|
disabled={jumlah < 10 || jumlah > sisaLembar}
|
||||||
|
onPress={async () => {
|
||||||
|
try {
|
||||||
|
setIsLoading(true);
|
||||||
|
await AsyncStorage.setItem(
|
||||||
|
LOCAL_STORAGE_KEY.transactionInvestment,
|
||||||
|
JSON.stringify({ jumlah, total })
|
||||||
|
);
|
||||||
|
router.push(`/investment/${id}/select-bank`);
|
||||||
|
} catch (error) {
|
||||||
|
console.log("[ERROR]", error);
|
||||||
|
} finally {
|
||||||
|
setIsLoading(false);
|
||||||
|
}
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
Beli {value}, {sisaLembar}
|
Beli
|
||||||
</ButtonCustom>
|
</ButtonCustom>
|
||||||
</BoxButtonOnFooter>
|
</BoxButtonOnFooter>
|
||||||
</>
|
</>
|
||||||
@@ -126,7 +143,7 @@ export default function InvestmentInvest() {
|
|||||||
}}
|
}}
|
||||||
placeholder="0"
|
placeholder="0"
|
||||||
keyboardType="numeric"
|
keyboardType="numeric"
|
||||||
value={value.toString()}
|
value={jumlah.toString()}
|
||||||
onChangeText={(value) => {
|
onChangeText={(value) => {
|
||||||
handleTextChange(value);
|
handleTextChange(value);
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
import {
|
import {
|
||||||
BaseBox,
|
BaseBox,
|
||||||
ButtonCenteredOnly,
|
ButtonCenteredOnly,
|
||||||
@@ -9,11 +10,96 @@ import {
|
|||||||
TextCustom,
|
TextCustom,
|
||||||
ViewWrapper,
|
ViewWrapper,
|
||||||
} from "@/components";
|
} from "@/components";
|
||||||
|
import CopyButton from "@/components/Button/CoyButton";
|
||||||
import { MainColor } from "@/constants/color-palet";
|
import { MainColor } from "@/constants/color-palet";
|
||||||
import { router, useLocalSearchParams } from "expo-router";
|
import DIRECTORY_ID from "@/constants/directory-id";
|
||||||
|
import {
|
||||||
|
apiInvestmentGetInvoice,
|
||||||
|
apiInvestmentUpdateInvoice,
|
||||||
|
} from "@/service/api-client/api-investment";
|
||||||
|
import { uploadFileService } from "@/service/upload-service";
|
||||||
|
import { formatCurrencyDisplay } from "@/utils/formatCurrencyDisplay";
|
||||||
|
import pickFile, { IFileData } from "@/utils/pickFile";
|
||||||
|
import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
|
||||||
|
import { useCallback, useState } from "react";
|
||||||
|
import { View } from "react-native";
|
||||||
|
import Toast from "react-native-toast-message";
|
||||||
|
|
||||||
export default function InvestmentInvoice() {
|
export default function InvestmentInvoice() {
|
||||||
const { id } = useLocalSearchParams();
|
const { id } = useLocalSearchParams();
|
||||||
|
console.log("[ID]", id);
|
||||||
|
const [data, setData] = useState<any>({});
|
||||||
|
const [image, setImage] = useState<IFileData>({
|
||||||
|
name: "",
|
||||||
|
uri: "",
|
||||||
|
size: 0,
|
||||||
|
});
|
||||||
|
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||||
|
|
||||||
|
useFocusEffect(
|
||||||
|
useCallback(() => {
|
||||||
|
onLoadData();
|
||||||
|
}, [id])
|
||||||
|
);
|
||||||
|
|
||||||
|
const onLoadData = async () => {
|
||||||
|
try {
|
||||||
|
const response = await apiInvestmentGetInvoice({
|
||||||
|
id: id as string,
|
||||||
|
category: "invoice",
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log("[RES INVOICE]", JSON.stringify(response.data, null, 2));
|
||||||
|
setData(response.data);
|
||||||
|
} catch (error) {
|
||||||
|
console.log("[ERROR]", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handlerSubmitUpdate = async () => {
|
||||||
|
try {
|
||||||
|
setIsLoading(true);
|
||||||
|
const responseUploadImage = await uploadFileService({
|
||||||
|
dirId: DIRECTORY_ID.investasi_bukti_transfer,
|
||||||
|
imageUri: image?.uri,
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log("[RESPONSE UPLOAD IMAGE]", responseUploadImage);
|
||||||
|
|
||||||
|
if (!responseUploadImage?.data?.id) {
|
||||||
|
Toast.show({
|
||||||
|
type: "error",
|
||||||
|
text1: "Gagal mengunggah bukti transfer",
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await apiInvestmentUpdateInvoice({
|
||||||
|
id: id as string,
|
||||||
|
data: {
|
||||||
|
imageId: responseUploadImage?.data?.id,
|
||||||
|
},
|
||||||
|
status: "proses",
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.success) {
|
||||||
|
console.log(
|
||||||
|
"[RESPONSE UPDATE]",
|
||||||
|
JSON.stringify(response.data, null, 2)
|
||||||
|
);
|
||||||
|
Toast.show({
|
||||||
|
type: "success",
|
||||||
|
text1: "Berhasil mengunggah bukti transfer",
|
||||||
|
});
|
||||||
|
router.push(`/investment/${id}/(transaction-flow)/process`);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log("[ERROR]", error);
|
||||||
|
} finally {
|
||||||
|
setIsLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ViewWrapper>
|
<ViewWrapper>
|
||||||
@@ -21,9 +107,23 @@ export default function InvestmentInvoice() {
|
|||||||
<InformationBox text="Mohon transfer ke rekening dibawah" />
|
<InformationBox text="Mohon transfer ke rekening dibawah" />
|
||||||
<BaseBox>
|
<BaseBox>
|
||||||
<StackCustom gap={"xs"}>
|
<StackCustom gap={"xs"}>
|
||||||
<TextCustom>Nama BANK</TextCustom>
|
<Grid>
|
||||||
<TextCustom>Nama Penerima</TextCustom>
|
<Grid.Col span={4}>
|
||||||
|
<TextCustom>Bank</TextCustom>
|
||||||
|
</Grid.Col>
|
||||||
|
<Grid.Col span={8}>
|
||||||
|
<TextCustom>{data?.MasterBank?.namaBank}</TextCustom>
|
||||||
|
</Grid.Col>
|
||||||
|
</Grid>
|
||||||
<Spacing height={10} />
|
<Spacing height={10} />
|
||||||
|
<Grid>
|
||||||
|
<Grid.Col span={4}>
|
||||||
|
<TextCustom>Nama Akun</TextCustom>
|
||||||
|
</Grid.Col>
|
||||||
|
<Grid.Col span={8}>
|
||||||
|
<TextCustom>{data?.MasterBank?.namaAkun}</TextCustom>
|
||||||
|
</Grid.Col>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
<BaseBox backgroundColor={MainColor.soft_darkblue}>
|
<BaseBox backgroundColor={MainColor.soft_darkblue}>
|
||||||
<Grid containerStyle={{ justifyContent: "center" }}>
|
<Grid containerStyle={{ justifyContent: "center" }}>
|
||||||
@@ -34,7 +134,7 @@ export default function InvestmentInvoice() {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<TextCustom size="xlarge" bold color="yellow">
|
<TextCustom size="xlarge" bold color="yellow">
|
||||||
4567898765433567
|
{data?.MasterBank?.norek}
|
||||||
</TextCustom>
|
</TextCustom>
|
||||||
</Grid.Col>
|
</Grid.Col>
|
||||||
<Grid.Col
|
<Grid.Col
|
||||||
@@ -43,7 +143,7 @@ export default function InvestmentInvoice() {
|
|||||||
alignItems: "flex-end",
|
alignItems: "flex-end",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<ButtonCustom>Salin</ButtonCustom>
|
<CopyButton textToCopy={data?.MasterBank?.norek} />
|
||||||
</Grid.Col>
|
</Grid.Col>
|
||||||
</Grid>
|
</Grid>
|
||||||
</BaseBox>
|
</BaseBox>
|
||||||
@@ -65,7 +165,7 @@ export default function InvestmentInvoice() {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<TextCustom size="xlarge" bold color="yellow">
|
<TextCustom size="xlarge" bold color="yellow">
|
||||||
Rp. 1.000.000
|
Rp. {formatCurrencyDisplay(data?.nominal)}
|
||||||
</TextCustom>
|
</TextCustom>
|
||||||
</Grid.Col>
|
</Grid.Col>
|
||||||
<Grid.Col
|
<Grid.Col
|
||||||
@@ -74,7 +174,7 @@ export default function InvestmentInvoice() {
|
|||||||
alignItems: "flex-end",
|
alignItems: "flex-end",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<ButtonCustom>Salin</ButtonCustom>
|
<CopyButton textToCopy={data?.nominal} />
|
||||||
</Grid.Col>
|
</Grid.Col>
|
||||||
</Grid>
|
</Grid>
|
||||||
</BaseBox>
|
</BaseBox>
|
||||||
@@ -83,10 +183,37 @@ export default function InvestmentInvoice() {
|
|||||||
|
|
||||||
<BaseBox>
|
<BaseBox>
|
||||||
<StackCustom>
|
<StackCustom>
|
||||||
<TextCustom>Upload bukti transfer anda.</TextCustom>
|
<TextCustom align="center">
|
||||||
|
Upload bukti transfer anda.
|
||||||
|
</TextCustom>
|
||||||
|
{image ? (
|
||||||
|
<View
|
||||||
|
style={{
|
||||||
|
flexDirection: "row",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
gap: 10,
|
||||||
|
paddingInline: 20,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<TextCustom bold align="center" truncate>
|
||||||
|
{image?.name}
|
||||||
|
</TextCustom>
|
||||||
|
</View>
|
||||||
|
) : (
|
||||||
|
<TextCustom align="center">
|
||||||
|
Tidak ada gambar yang diunggah
|
||||||
|
</TextCustom>
|
||||||
|
)}
|
||||||
<ButtonCenteredOnly
|
<ButtonCenteredOnly
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
router.push("/(application)/(image)/take-picture/123");
|
pickFile({
|
||||||
|
allowedType: "image",
|
||||||
|
setImageUri(file: any) {
|
||||||
|
console.log("[IMAGE]", file);
|
||||||
|
setImage(file);
|
||||||
|
},
|
||||||
|
});
|
||||||
}}
|
}}
|
||||||
icon="upload"
|
icon="upload"
|
||||||
>
|
>
|
||||||
@@ -96,14 +223,16 @@ export default function InvestmentInvoice() {
|
|||||||
</BaseBox>
|
</BaseBox>
|
||||||
|
|
||||||
<ButtonCustom
|
<ButtonCustom
|
||||||
|
isLoading={isLoading}
|
||||||
|
disabled={!image}
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
router.push(`/investment/${id}/(transaction-flow)/process`);
|
handlerSubmitUpdate();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Saya Sudah Transfer
|
Saya Sudah Transfer
|
||||||
</ButtonCustom>
|
</ButtonCustom>
|
||||||
</StackCustom>
|
</StackCustom>
|
||||||
<Spacing/>
|
<Spacing />
|
||||||
</ViewWrapper>
|
</ViewWrapper>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,12 +1,10 @@
|
|||||||
import {
|
import {
|
||||||
BaseBox,
|
BaseBox,
|
||||||
Grid,
|
|
||||||
StackCustom,
|
StackCustom,
|
||||||
TextCustom,
|
TextCustom,
|
||||||
ViewWrapper,
|
ViewWrapper
|
||||||
} from "@/components";
|
} from "@/components";
|
||||||
import { MainColor } from "@/constants/color-palet";
|
import { MainColor } from "@/constants/color-palet";
|
||||||
import { Ionicons } from "@expo/vector-icons";
|
|
||||||
import { ActivityIndicator } from "react-native";
|
import { ActivityIndicator } from "react-native";
|
||||||
|
|
||||||
export default function InvestmentProcess() {
|
export default function InvestmentProcess() {
|
||||||
@@ -16,21 +14,22 @@ export default function InvestmentProcess() {
|
|||||||
<BaseBox>
|
<BaseBox>
|
||||||
<StackCustom>
|
<StackCustom>
|
||||||
<TextCustom align="center" bold>
|
<TextCustom align="center" bold>
|
||||||
Admin sedang memproses transaksi investasimu
|
Admin sedang memvalidasi data dan bukti transfer anda. Mohon
|
||||||
|
tunggu proses ini selesai.
|
||||||
</TextCustom>
|
</TextCustom>
|
||||||
<ActivityIndicator size="large" color={MainColor.yellow} />
|
<ActivityIndicator size="large" color={MainColor.yellow} />
|
||||||
</StackCustom>
|
</StackCustom>
|
||||||
</BaseBox>
|
</BaseBox>
|
||||||
|
|
||||||
<BaseBox>
|
{/* <BaseBox>
|
||||||
<Grid>
|
<Grid>
|
||||||
<Grid.Col span={10} style={{justifyContent: 'center'}}>
|
<Grid.Col span={10} style={{ justifyContent: "center" }}>
|
||||||
<TextCustom size="small">
|
<TextCustom size="small">
|
||||||
Hubungi admin jika tidak kunjung di proses! Klik pada logo
|
Hubungi admin jika tidak kunjung di proses! Klik pada logo
|
||||||
Whatsapp ini.
|
Whatsapp ini.
|
||||||
</TextCustom>
|
</TextCustom>
|
||||||
</Grid.Col>
|
</Grid.Col>
|
||||||
<Grid.Col span={2} style={{alignItems: "flex-end"}}>
|
<Grid.Col span={2} style={{ alignItems: "flex-end" }}>
|
||||||
<Ionicons
|
<Ionicons
|
||||||
name="logo-whatsapp"
|
name="logo-whatsapp"
|
||||||
size={50}
|
size={50}
|
||||||
@@ -38,7 +37,7 @@ export default function InvestmentProcess() {
|
|||||||
/>
|
/>
|
||||||
</Grid.Col>
|
</Grid.Col>
|
||||||
</Grid>
|
</Grid>
|
||||||
</BaseBox>
|
</BaseBox> */}
|
||||||
</ViewWrapper>
|
</ViewWrapper>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -5,35 +5,97 @@ import {
|
|||||||
ViewWrapper,
|
ViewWrapper,
|
||||||
} from "@/components";
|
} from "@/components";
|
||||||
import { RadioCustom, RadioGroup } from "@/components/Radio/RadioCustom";
|
import { RadioCustom, RadioGroup } from "@/components/Radio/RadioCustom";
|
||||||
import { dummyMasterBank } from "@/lib/dummy-data/_master/bank";
|
import { LOCAL_STORAGE_KEY } from "@/constants/local-storage-key";
|
||||||
|
import { useAuth } from "@/hooks/use-auth";
|
||||||
|
import { apiInvestmentCreateInvoice } from "@/service/api-client/api-investment";
|
||||||
|
import { apiMasterBank } from "@/service/api-client/api-master";
|
||||||
|
import AsyncStorage from "@react-native-async-storage/async-storage";
|
||||||
import { router, useLocalSearchParams } from "expo-router";
|
import { router, useLocalSearchParams } from "expo-router";
|
||||||
import { useState } from "react";
|
import _ from "lodash";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
export default function InvestmentSelectBank() {
|
export default function InvestmentSelectBank() {
|
||||||
|
const { user } = useAuth();
|
||||||
const { id } = useLocalSearchParams();
|
const { id } = useLocalSearchParams();
|
||||||
const [value, setValue] = useState<any | number>("");
|
const [select, setSelect] = useState<any | number>("");
|
||||||
|
const [listBank, setListBank] = useState<any>([]);
|
||||||
|
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
loadListBank();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const loadListBank = async () => {
|
||||||
|
try {
|
||||||
|
const response = await apiMasterBank();
|
||||||
|
|
||||||
|
setListBank(response.data);
|
||||||
|
} catch (error) {
|
||||||
|
console.log("[ERROR]", error);
|
||||||
|
setListBank([]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handlerSubmit = async () => {
|
||||||
|
try {
|
||||||
|
setIsLoading(true);
|
||||||
|
const dataCheckout = await AsyncStorage.getItem(
|
||||||
|
LOCAL_STORAGE_KEY.transactionInvestment
|
||||||
|
);
|
||||||
|
if (dataCheckout) {
|
||||||
|
const storage = JSON.parse(dataCheckout);
|
||||||
|
const newData = {
|
||||||
|
...storage,
|
||||||
|
bankId: select,
|
||||||
|
authorId: user?.id,
|
||||||
|
};
|
||||||
|
|
||||||
|
const response = await apiInvestmentCreateInvoice({
|
||||||
|
id: id as string,
|
||||||
|
data: newData,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.success) {
|
||||||
|
console.log("[RESPONSE >>]", response);
|
||||||
|
const invoiceId = response.data.id;
|
||||||
|
|
||||||
|
const delStorage = await AsyncStorage.removeItem(
|
||||||
|
LOCAL_STORAGE_KEY.transactionInvestment
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log("[DEL STORAGE]", delStorage);
|
||||||
|
router.replace(`/investment/${invoiceId}/invoice`);
|
||||||
|
} else {
|
||||||
|
console.log("[FAILED]", response);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log("[ERROR]", error);
|
||||||
|
} finally {
|
||||||
|
setIsLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const buttonSubmit = () => {
|
const buttonSubmit = () => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<BoxButtonOnFooter>
|
<BoxButtonOnFooter>
|
||||||
<ButtonCustom
|
<ButtonCustom isLoading={isLoading} onPress={() => handlerSubmit()}>Pilih</ButtonCustom>
|
||||||
onPress={() => router.replace(`/investment/${id}/invoice`)}
|
|
||||||
>
|
|
||||||
Pilih
|
|
||||||
</ButtonCustom>
|
|
||||||
</BoxButtonOnFooter>
|
</BoxButtonOnFooter>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ViewWrapper footerComponent={buttonSubmit()}>
|
<ViewWrapper footerComponent={buttonSubmit()}>
|
||||||
<RadioGroup value={value} onChange={setValue}>
|
<RadioGroup value={select} onChange={setSelect}>
|
||||||
{dummyMasterBank.map((item) => (
|
{_.isEmpty(listBank)
|
||||||
<BaseBox key={item.name}>
|
? []
|
||||||
<RadioCustom label={item.name} value={item.code} />
|
: listBank?.map((item: any) => (
|
||||||
</BaseBox>
|
<BaseBox key={item.id}>
|
||||||
))}
|
<RadioCustom label={item.namaBank} value={item.id} />
|
||||||
|
</BaseBox>
|
||||||
|
))}
|
||||||
</RadioGroup>
|
</RadioGroup>
|
||||||
</ViewWrapper>
|
</ViewWrapper>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -101,9 +101,8 @@ export default function InvestmentEdit() {
|
|||||||
|
|
||||||
const displayTargetDana = formatCurrencyDisplay(data?.targetDana);
|
const displayTargetDana = formatCurrencyDisplay(data?.targetDana);
|
||||||
const displayHargaPerLembar = formatCurrencyDisplay(data?.hargaLembar);
|
const displayHargaPerLembar = formatCurrencyDisplay(data?.hargaLembar);
|
||||||
const displayTotalLembar = formatCurrencyDisplay(
|
const realTotalLembar = Number(data?.targetDana) / Number(data?.hargaLembar);
|
||||||
Number(data?.targetDana) / Number(data?.hargaLembar)
|
const displayTotalLembar = formatCurrencyDisplay(realTotalLembar);
|
||||||
);
|
|
||||||
|
|
||||||
const handleChangeCurrency = (field: keyof typeof data) => (text: string) => {
|
const handleChangeCurrency = (field: keyof typeof data) => (text: string) => {
|
||||||
const numeric = text.replace(/\D/g, "");
|
const numeric = text.replace(/\D/g, "");
|
||||||
@@ -134,6 +133,7 @@ export default function InvestmentEdit() {
|
|||||||
const handleSubmitUpdate = async () => {
|
const handleSubmitUpdate = async () => {
|
||||||
let newData = {
|
let newData = {
|
||||||
...data,
|
...data,
|
||||||
|
totalLembar: realTotalLembar.toString(),
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!validateData()) {
|
if (!validateData()) {
|
||||||
|
|||||||
@@ -74,9 +74,8 @@ export default function InvestmentCreate() {
|
|||||||
|
|
||||||
const displayTargetDana = formatCurrencyDisplay(data.targetDana);
|
const displayTargetDana = formatCurrencyDisplay(data.targetDana);
|
||||||
const displayHargaPerLembar = formatCurrencyDisplay(data.hargaPerLembar);
|
const displayHargaPerLembar = formatCurrencyDisplay(data.hargaPerLembar);
|
||||||
const displayTotalLembar = formatCurrencyDisplay(
|
const realTotalLembar = Number(data.targetDana) / Number(data.hargaPerLembar);
|
||||||
Number(data.targetDana) / Number(data.hargaPerLembar)
|
const displayTotalLembar = formatCurrencyDisplay(realTotalLembar);
|
||||||
);
|
|
||||||
|
|
||||||
const handleChangeCurrency = (field: keyof typeof data) => (text: string) => {
|
const handleChangeCurrency = (field: keyof typeof data) => (text: string) => {
|
||||||
const numeric = text.replace(/\D/g, "");
|
const numeric = text.replace(/\D/g, "");
|
||||||
@@ -150,7 +149,7 @@ export default function InvestmentCreate() {
|
|||||||
title: data.title,
|
title: data.title,
|
||||||
targetDana: data.targetDana,
|
targetDana: data.targetDana,
|
||||||
hargaLembar: data.hargaPerLembar,
|
hargaLembar: data.hargaPerLembar,
|
||||||
totalLembar: displayTotalLembar,
|
totalLembar: realTotalLembar.toString(),
|
||||||
roi: data.rasioKeuntungan,
|
roi: data.rasioKeuntungan,
|
||||||
masterPencarianInvestorId: data.pencarianInvestor,
|
masterPencarianInvestorId: data.pencarianInvestor,
|
||||||
masterPembagianDevidenId: data.pembagianDeviden,
|
masterPembagianDevidenId: data.pembagianDeviden,
|
||||||
|
|||||||
72
components/Button/CoyButton.tsx
Normal file
72
components/Button/CoyButton.tsx
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
import React, { useState } from "react";
|
||||||
|
import { TouchableOpacity, Text, StyleSheet } from "react-native";
|
||||||
|
import * as Clipboard from "expo-clipboard";
|
||||||
|
import { MainColor } from "@/constants/color-palet";
|
||||||
|
|
||||||
|
interface CopyButtonProps {
|
||||||
|
textToCopy: string;
|
||||||
|
copyLabel?: string; // Teks tombol saat normal, default: "Copy"
|
||||||
|
copiedLabel?: string; // Teks tombol saat berhasil, default: "Copied!"
|
||||||
|
onCopySuccess?: () => void; // Callback opsional saat berhasil menyalin
|
||||||
|
duration?: number; // Durasi (ms) menampilkan "Copied!", default: 2000
|
||||||
|
style?: any; // Gaya tambahan untuk tombol
|
||||||
|
textStyle?: any; // Gaya tambahan untuk teks
|
||||||
|
}
|
||||||
|
|
||||||
|
const CopyButton: React.FC<CopyButtonProps> = ({
|
||||||
|
textToCopy,
|
||||||
|
copyLabel = "Copy",
|
||||||
|
copiedLabel = "Copied!",
|
||||||
|
onCopySuccess,
|
||||||
|
duration = 2000,
|
||||||
|
style,
|
||||||
|
textStyle,
|
||||||
|
}) => {
|
||||||
|
const [isCopied, setIsCopied] = useState(false);
|
||||||
|
|
||||||
|
const handleCopy = async () => {
|
||||||
|
try {
|
||||||
|
await Clipboard.setStringAsync(textToCopy);
|
||||||
|
setIsCopied(true);
|
||||||
|
onCopySuccess?.();
|
||||||
|
|
||||||
|
// Reset kembali ke label "Copy" setelah durasi tertentu
|
||||||
|
setTimeout(() => {
|
||||||
|
setIsCopied(false);
|
||||||
|
}, duration);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to copy text:", error);
|
||||||
|
// Opsional: tampilkan error toast jika diperlukan
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TouchableOpacity
|
||||||
|
style={[styles.button, style]}
|
||||||
|
onPress={handleCopy}
|
||||||
|
activeOpacity={0.7}
|
||||||
|
>
|
||||||
|
<Text style={[styles.text, textStyle]}>
|
||||||
|
{isCopied ? copiedLabel : copyLabel}
|
||||||
|
</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
button: {
|
||||||
|
backgroundColor: MainColor.yellow,
|
||||||
|
paddingHorizontal: 16,
|
||||||
|
paddingVertical: 8,
|
||||||
|
borderRadius: 50,
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
},
|
||||||
|
text: {
|
||||||
|
color: MainColor.darkblue,
|
||||||
|
fontWeight: "600",
|
||||||
|
fontSize: 14,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default CopyButton;
|
||||||
4
constants/local-storage-key.ts
Normal file
4
constants/local-storage-key.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
export const LOCAL_STORAGE_KEY = {
|
||||||
|
transactionInvestment: "transactionInvestment",
|
||||||
|
transactionDonation: "transactionDonation",
|
||||||
|
};
|
||||||
@@ -17,15 +17,15 @@ const listDataNotPublishInvesment = ({ data }: { data: any }) => [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Total Lembar",
|
label: "Total Lembar",
|
||||||
value: data?.totalLembar || "-",
|
value: formatCurrencyDisplay(data?.totalLembar) || "-",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Pencarian Investor",
|
label: "Pencarian Investor",
|
||||||
value: data && data?.MasterPencarianInvestor?.name + " hari" || "-",
|
value: (data && data?.MasterPencarianInvestor?.name + " hari") || "-",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Jadwal Pembagian",
|
label: "Jadwal Pembagian",
|
||||||
value: data && data?.MasterPembagianDeviden?.name + " bulan" || "-",
|
value: (data && data?.MasterPembagianDeviden?.name + " bulan") || "-",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Pembagian Deviden",
|
label: "Pembagian Deviden",
|
||||||
@@ -52,11 +52,11 @@ const listDataPublishInvesment = ({ data }: { data: any }) => [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Total Lembar",
|
label: "Total Lembar",
|
||||||
value: data?.totalLembar || "-",
|
value: formatCurrencyDisplay(data?.totalLembar) || "-",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Sisa Lembar",
|
label: "Sisa Lembar",
|
||||||
value: data?.sisaLembar || "-",
|
value: formatCurrencyDisplay(data?.sisaLembar) || "-",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Pencarian Investor",
|
label: "Pencarian Investor",
|
||||||
|
|||||||
@@ -137,3 +137,67 @@ export async function apiInvestmentGetAll() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function apiInvestmentCreateInvoice({
|
||||||
|
id,
|
||||||
|
data,
|
||||||
|
}: {
|
||||||
|
id: string;
|
||||||
|
data: any;
|
||||||
|
}) {
|
||||||
|
try {
|
||||||
|
const response = await apiConfig.post(
|
||||||
|
`/mobile/investment/${id}/invoice`,
|
||||||
|
{
|
||||||
|
data: data,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function apiInvestmentGetInvoice({
|
||||||
|
id,
|
||||||
|
authorId,
|
||||||
|
category,
|
||||||
|
}: {
|
||||||
|
id?: string;
|
||||||
|
authorId?: string;
|
||||||
|
category: "my-invest" | "transaction" | "invoice";
|
||||||
|
}) {
|
||||||
|
const categoryQuery = `?category=${category}`;
|
||||||
|
const authorIdQuery = authorId ? `&authorId=${authorId}` : "";
|
||||||
|
try {
|
||||||
|
const response = await apiConfig.get(
|
||||||
|
`/mobile/investment/${id}/invoice${categoryQuery}${authorIdQuery}`
|
||||||
|
);
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function apiInvestmentUpdateInvoice({
|
||||||
|
id,
|
||||||
|
data,
|
||||||
|
status,
|
||||||
|
}: {
|
||||||
|
id: string;
|
||||||
|
data: {
|
||||||
|
imageId?: string;
|
||||||
|
};
|
||||||
|
status: "berhasil" | "gagal" | "proses" | "menunggu";
|
||||||
|
}) {
|
||||||
|
try {
|
||||||
|
const response = await apiConfig.put(
|
||||||
|
`/mobile/investment/${id}/invoice?status=${status}`,
|
||||||
|
{
|
||||||
|
data: data,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -110,7 +110,11 @@ export async function apiForumCreateReportCommentar({
|
|||||||
export async function apiMasterInvestment({
|
export async function apiMasterInvestment({
|
||||||
category,
|
category,
|
||||||
}: {
|
}: {
|
||||||
category?: "pencarian-investor" | "periode-deviden" | "pembagian-deviden" | string;
|
category?:
|
||||||
|
| "pencarian-investor"
|
||||||
|
| "periode-deviden"
|
||||||
|
| "pembagian-deviden"
|
||||||
|
| string;
|
||||||
}) {
|
}) {
|
||||||
const selectCategory = category ? `?category=${category}` : "";
|
const selectCategory = category ? `?category=${category}` : "";
|
||||||
try {
|
try {
|
||||||
@@ -122,3 +126,12 @@ export async function apiMasterInvestment({
|
|||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function apiMasterBank() {
|
||||||
|
try {
|
||||||
|
const response = await apiConfig.get(`/mobile/master/bank`);
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user