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:
2025-10-02 17:29:25 +08:00
parent aa85e05f79
commit 2be4afdcb1
12 changed files with 514 additions and 113 deletions

View File

@@ -10,8 +10,10 @@ import {
TextInputCustom,
ViewWrapper,
} from "@/components";
import { LOCAL_STORAGE_KEY } from "@/constants/local-storage-key";
import { apiInvestmentGetOne } from "@/service/api-client/api-investment";
import { formatCurrencyDisplay } from "@/utils/formatCurrencyDisplay";
import AsyncStorage from "@react-native-async-storage/async-storage";
import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
import { useCallback, useState } from "react";
@@ -19,9 +21,10 @@ export default function InvestmentInvest() {
const { id } = useLocalSearchParams();
// console.log("[ID]", id);
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 [sisaLembar, setSisaLembar] = useState<number>(0);
const [isLoading, setIsLoading] = useState<boolean>(false);
useFocusEffect(
useCallback(() => {
@@ -45,7 +48,7 @@ export default function InvestmentInvest() {
const handleTextChange = (text: string) => {
// Izinkan input kosong → anggap sebagai 0 (atau abaikan, tergantung UX)
if (text === "") {
setValue(0);
setJumlah(0);
setTotal(0);
return;
}
@@ -58,7 +61,7 @@ export default function InvestmentInvest() {
// Karena regex sudah pastikan hanya angka, isNaN biasanya false
// Tapi tetap aman untuk cek
if (!isNaN(numValue)) {
setValue(numValue);
setJumlah(numValue);
setTotal(numValue * Number(data?.hargaLembar));
console.log("[VALUE]", numValue);
}
@@ -71,10 +74,24 @@ export default function InvestmentInvest() {
<>
<BoxButtonOnFooter>
<ButtonCustom
disabled={value < 10 || value >= sisaLembar}
onPress={() => router.push(`/investment/${id}/select-bank`)}
isLoading={isLoading}
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>
</BoxButtonOnFooter>
</>
@@ -126,7 +143,7 @@ export default function InvestmentInvest() {
}}
placeholder="0"
keyboardType="numeric"
value={value.toString()}
value={jumlah.toString()}
onChangeText={(value) => {
handleTextChange(value);
}}

View File

@@ -1,3 +1,4 @@
/* eslint-disable react-hooks/exhaustive-deps */
import {
BaseBox,
ButtonCenteredOnly,
@@ -9,11 +10,96 @@ import {
TextCustom,
ViewWrapper,
} from "@/components";
import CopyButton from "@/components/Button/CoyButton";
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() {
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 (
<>
<ViewWrapper>
@@ -21,9 +107,23 @@ export default function InvestmentInvoice() {
<InformationBox text="Mohon transfer ke rekening dibawah" />
<BaseBox>
<StackCustom gap={"xs"}>
<TextCustom>Nama BANK</TextCustom>
<TextCustom>Nama Penerima</TextCustom>
<Grid>
<Grid.Col span={4}>
<TextCustom>Bank</TextCustom>
</Grid.Col>
<Grid.Col span={8}>
<TextCustom>{data?.MasterBank?.namaBank}</TextCustom>
</Grid.Col>
</Grid>
<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}>
<Grid containerStyle={{ justifyContent: "center" }}>
@@ -34,7 +134,7 @@ export default function InvestmentInvoice() {
}}
>
<TextCustom size="xlarge" bold color="yellow">
4567898765433567
{data?.MasterBank?.norek}
</TextCustom>
</Grid.Col>
<Grid.Col
@@ -43,7 +143,7 @@ export default function InvestmentInvoice() {
alignItems: "flex-end",
}}
>
<ButtonCustom>Salin</ButtonCustom>
<CopyButton textToCopy={data?.MasterBank?.norek} />
</Grid.Col>
</Grid>
</BaseBox>
@@ -65,7 +165,7 @@ export default function InvestmentInvoice() {
}}
>
<TextCustom size="xlarge" bold color="yellow">
Rp. 1.000.000
Rp. {formatCurrencyDisplay(data?.nominal)}
</TextCustom>
</Grid.Col>
<Grid.Col
@@ -74,7 +174,7 @@ export default function InvestmentInvoice() {
alignItems: "flex-end",
}}
>
<ButtonCustom>Salin</ButtonCustom>
<CopyButton textToCopy={data?.nominal} />
</Grid.Col>
</Grid>
</BaseBox>
@@ -83,10 +183,37 @@ export default function InvestmentInvoice() {
<BaseBox>
<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
onPress={() => {
router.push("/(application)/(image)/take-picture/123");
pickFile({
allowedType: "image",
setImageUri(file: any) {
console.log("[IMAGE]", file);
setImage(file);
},
});
}}
icon="upload"
>
@@ -96,14 +223,16 @@ export default function InvestmentInvoice() {
</BaseBox>
<ButtonCustom
isLoading={isLoading}
disabled={!image}
onPress={() => {
router.push(`/investment/${id}/(transaction-flow)/process`);
handlerSubmitUpdate();
}}
>
Saya Sudah Transfer
</ButtonCustom>
</StackCustom>
<Spacing/>
<Spacing />
</ViewWrapper>
</>
);

View File

@@ -1,12 +1,10 @@
import {
BaseBox,
Grid,
StackCustom,
TextCustom,
ViewWrapper,
ViewWrapper
} from "@/components";
import { MainColor } from "@/constants/color-palet";
import { Ionicons } from "@expo/vector-icons";
import { ActivityIndicator } from "react-native";
export default function InvestmentProcess() {
@@ -16,21 +14,22 @@ export default function InvestmentProcess() {
<BaseBox>
<StackCustom>
<TextCustom align="center" bold>
Admin sedang memproses transaksi investasimu
Admin sedang memvalidasi data dan bukti transfer anda. Mohon
tunggu proses ini selesai.
</TextCustom>
<ActivityIndicator size="large" color={MainColor.yellow} />
</StackCustom>
</BaseBox>
<BaseBox>
{/* <BaseBox>
<Grid>
<Grid.Col span={10} style={{justifyContent: 'center'}}>
<Grid.Col span={10} style={{ justifyContent: "center" }}>
<TextCustom size="small">
Hubungi admin jika tidak kunjung di proses! Klik pada logo
Whatsapp ini.
</TextCustom>
</Grid.Col>
<Grid.Col span={2} style={{alignItems: "flex-end"}}>
<Grid.Col span={2} style={{ alignItems: "flex-end" }}>
<Ionicons
name="logo-whatsapp"
size={50}
@@ -38,7 +37,7 @@ export default function InvestmentProcess() {
/>
</Grid.Col>
</Grid>
</BaseBox>
</BaseBox> */}
</ViewWrapper>
</>
);

View File

@@ -5,35 +5,97 @@ import {
ViewWrapper,
} from "@/components";
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 { useState } from "react";
import _ from "lodash";
import { useEffect, useState } from "react";
export default function InvestmentSelectBank() {
const { user } = useAuth();
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 = () => {
return (
<>
<BoxButtonOnFooter>
<ButtonCustom
onPress={() => router.replace(`/investment/${id}/invoice`)}
>
Pilih
</ButtonCustom>
<ButtonCustom isLoading={isLoading} onPress={() => handlerSubmit()}>Pilih</ButtonCustom>
</BoxButtonOnFooter>
</>
);
};
return (
<ViewWrapper footerComponent={buttonSubmit()}>
<RadioGroup value={value} onChange={setValue}>
{dummyMasterBank.map((item) => (
<BaseBox key={item.name}>
<RadioCustom label={item.name} value={item.code} />
</BaseBox>
))}
<RadioGroup value={select} onChange={setSelect}>
{_.isEmpty(listBank)
? []
: listBank?.map((item: any) => (
<BaseBox key={item.id}>
<RadioCustom label={item.namaBank} value={item.id} />
</BaseBox>
))}
</RadioGroup>
</ViewWrapper>
);