Add:
- (user)/donation/[id]/(transaction-flow)/[invoiceId]

Fix:
Integrasi dalam alur transaksi
- Buat invoice dan list transaksi

### No Issue
This commit is contained in:
2025-10-07 17:40:54 +08:00
parent 7c82e8b588
commit a980397640
11 changed files with 284 additions and 86 deletions

View File

@@ -449,7 +449,7 @@ export default function UserLayout() {
}} }}
/> />
<Stack.Screen <Stack.Screen
name="donation/[id]/(transaction-flow)/[transaction]/invoice" name="donation/[id]/(transaction-flow)/[invoiceId]/invoice"
options={{ options={{
title: "Invoice", title: "Invoice",
headerLeft: () => ( headerLeft: () => (

View File

@@ -23,7 +23,9 @@ export default function DonationBeranda() {
const onLoadData = async () => { const onLoadData = async () => {
try { try {
setLoadList(true); setLoadList(true);
const response = await apiDonationGetAll(); const response = await apiDonationGetAll({
category: "beranda"
});
console.log("[RES GET ALL]", JSON.stringify(response.data, null, 2)); console.log("[RES GET ALL]", JSON.stringify(response.data, null, 2));
setList(response.data); setList(response.data);
@@ -44,7 +46,7 @@ export default function DonationBeranda() {
{loadList ? ( {loadList ? (
<LoaderCustom /> <LoaderCustom />
) : _.isEmpty(list) ? ( ) : _.isEmpty(list) ? (
<TextCustom>Belum ada data</TextCustom> <TextCustom align="center" color="gray">Belum ada donasi</TextCustom>
) : ( ) : (
list?.map((item: any, index: number) => ( list?.map((item: any, index: number) => (
<Donation_BoxPublish data={item} key={index} id={item.id} /> <Donation_BoxPublish data={item} key={index} id={item.id} />

View File

@@ -3,74 +3,124 @@ import {
BaseBox, BaseBox,
DummyLandscapeImage, DummyLandscapeImage,
Grid, Grid,
LoaderCustom,
StackCustom, StackCustom,
TextCustom, TextCustom,
ViewWrapper, ViewWrapper,
} from "@/components"; } from "@/components";
import { useAuth } from "@/hooks/use-auth";
import { dummyMasterStatusTransaction } from "@/lib/dummy-data/_master/status-transaction"; import { dummyMasterStatusTransaction } from "@/lib/dummy-data/_master/status-transaction";
import { router } from "expo-router"; import { apiDonationGetAll } from "@/service/api-client/api-donation";
import { formatCurrencyDisplay } from "@/utils/formatCurrencyDisplay";
import { Href, 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 DonationMyDonation() { export default function DonationMyDonation() {
const randomStatusData = Array.from({ length: 10 }, () => { const { user } = useAuth();
const randomIndex = Math.floor( const [list, setList] = useState<any[] | null>(null);
Math.random() * dummyMasterStatusTransaction.length const [loadList, setLoadList] = useState(false);
);
return dummyMasterStatusTransaction[randomIndex];
});
const handlePress = (value: string) => { useFocusEffect(
if (value === "menunggu") { useCallback(() => {
router.push(`/donation/${value}/(transaction-flow)/123/invoice`); onLoadData();
} else if (value === "proses") { }, [])
router.push(`/donation/${value}/(transaction-flow)/123/process`); );
} else if (value === "berhasil") {
router.push(`/donation/${value}/(transaction-flow)/123/success`); const onLoadData = async () => {
} else if (value === "gagal") { try {
router.push(`/donation/${value}/(transaction-flow)/123/failed`); setLoadList(true);
const response = await apiDonationGetAll({
category: "my-donation",
authorId: user?.id,
});
console.log(
"[RES GET MY DONATION]",
JSON.stringify(response.data, null, 2)
);
setList(response.data);
} catch (error) {
console.log("[ERROR]", error);
} finally {
setLoadList(false);
}
};
const handlePress = ({
invoiceId,
donationId,
status,
}: {
invoiceId: string;
donationId: string;
status: string;
}) => {
const url: Href = `../${donationId}/(transaction-flow)/${invoiceId}/invoice`;
if (status === "menunggu") {
router.push(url);
} else if (status === "proses") {
router.push(url);
} else if (status === "berhasil") {
router.push(url);
} else if (status === "gagal") {
router.push(url);
} }
}; };
return ( return (
<ViewWrapper hideFooter> <ViewWrapper hideFooter>
{randomStatusData.map((item, index) => ( {loadList ? (
<BaseBox <LoaderCustom />
key={index} ) : _.isEmpty(list) ? (
paddingTop={7} <TextCustom align="center" color="gray">
paddingBottom={7} Belum ada transaksi
onPress={() => { </TextCustom>
handlePress(item.value); ) : (
}} list?.map((item, index) => (
> <BaseBox
<Grid> key={index}
<Grid.Col span={5}> paddingTop={7}
<DummyLandscapeImage height={100} unClickPath /> paddingBottom={7}
</Grid.Col> onPress={() => {
<Grid.Col span={1}> handlePress({
<View /> status: _.lowerCase(item.statusInvoice),
</Grid.Col> invoiceId: item.id,
<Grid.Col span={6}> donationId: item.donasiId,
<StackCustom gap={"sm"}> });
<View> }}
<TextCustom truncate> >
Judul Donasi: Lorem ipsum dolor sit amet consectetur <Grid>
adipisicing elit. <Grid.Col span={5}>
<DummyLandscapeImage
height={100}
unClickPath
imageId={item.imageId}
/>
</Grid.Col>
<Grid.Col span={1}>
<View />
</Grid.Col>
<Grid.Col span={6}>
<StackCustom>
<TextCustom truncate={2} bold>
{item.title || "-"}
</TextCustom> </TextCustom>
</View>
<View>
<TextCustom>Donasi Saya</TextCustom>
<TextCustom bold color="yellow"> <TextCustom bold color="yellow">
Rp. 7.500.000 Rp. {formatCurrencyDisplay(item.nominal)}
</TextCustom> </TextCustom>
</View>
<BadgeCustom variant="light" color={item.color} fullWidth> <BadgeCustom variant="light" color={item.color} fullWidth>
{item.label} {item.statusInvoice}
</BadgeCustom> </BadgeCustom>
</StackCustom> </StackCustom>
</Grid.Col> </Grid.Col>
</Grid> </Grid>
</BaseBox> </BaseBox>
))} ))
)}
</ViewWrapper> </ViewWrapper>
); );
} }

View File

@@ -13,12 +13,15 @@ import { MainColor } from "@/constants/color-palet";
import { router, useLocalSearchParams } from "expo-router"; import { router, useLocalSearchParams } from "expo-router";
export default function DonationInvoice() { export default function DonationInvoice() {
const { id, transaction } = useLocalSearchParams(); const { invoiceId } = useLocalSearchParams();
console.log("invoiceId", invoiceId);
return ( return (
<> <>
<ViewWrapper> <ViewWrapper>
<StackCustom> <StackCustom>
<InformationBox text={`Mohon transfer donasi anda ke rekening dibawah dengan Id: ${transaction}`} /> <InformationBox
text={`Mohon transfer donasi anda ke rekening dibawah dengan Id: ${invoiceId}`}
/>
<BaseBox> <BaseBox>
<StackCustom gap={"xs"}> <StackCustom gap={"xs"}>
<TextCustom>Nama BANK</TextCustom> <TextCustom>Nama BANK</TextCustom>
@@ -97,7 +100,7 @@ export default function DonationInvoice() {
<ButtonCustom <ButtonCustom
onPress={() => { onPress={() => {
router.push(`/donation/${id}/(transaction-flow)/process`); router.push(`/donation/${invoiceId}/(transaction-flow)/process`);
}} }}
> >
Saya Sudah Transfer Saya Sudah Transfer

View File

@@ -9,15 +9,44 @@ import {
} from "@/components"; } from "@/components";
import { MainColor } from "@/constants/color-palet"; import { MainColor } from "@/constants/color-palet";
import { ICON_SIZE_SMALL } from "@/constants/constans-value"; import { ICON_SIZE_SMALL } from "@/constants/constans-value";
import { LOCAL_STORAGE_KEY } from "@/constants/local-storage-key";
import { formatCurrencyDisplay } from "@/utils/formatCurrencyDisplay";
import { Ionicons } from "@expo/vector-icons"; import { Ionicons } from "@expo/vector-icons";
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";
export default function InvestmentInputDonation() { export default function InvestmentInputDonation() {
const { id } = useLocalSearchParams(); const { id } = useLocalSearchParams();
const [nominal, setNominal] = useState<number>(0);
const handlerSubmit = async () => {
try {
console.log("jumlah", nominal);
await AsyncStorage.setItem(
LOCAL_STORAGE_KEY.transactionDonation,
JSON.stringify({ nominal: nominal.toString() })
);
router.replace(`/donation/${id}/select-bank`);
} catch (error) {
console.log("[ERROR]", error);
}
};
const displayJumlah = formatCurrencyDisplay(nominal);
const handleChangeCurrency = (text: string) => {
const numeric = text.replace(/\D/g, "");
setNominal(Number(numeric));
};
const bottomComponent = ( const bottomComponent = (
<BoxButtonOnFooter> <BoxButtonOnFooter>
<ButtonCustom <ButtonCustom
onPress={() => router.replace(`/donation/${id}/select-bank`)} disabled={nominal < 10000 || nominal === 0}
onPress={() => {
handlerSubmit();
}}
> >
Lanjutan Lanjutan
</ButtonCustom> </ButtonCustom>
@@ -27,7 +56,7 @@ export default function InvestmentInputDonation() {
<> <>
<ViewWrapper footerComponent={bottomComponent}> <ViewWrapper footerComponent={bottomComponent}>
{listData.map((item, i) => ( {listData.map((item, i) => (
<BaseBox key={i}> <BaseBox key={i} onPress={() => setNominal(item.value)}>
<Grid> <Grid>
<Grid.Col span={8}> <Grid.Col span={8}>
<TextCustom bold size="large"> <TextCustom bold size="large">
@@ -51,6 +80,8 @@ export default function InvestmentInputDonation() {
label="Nominal lainnya" label="Nominal lainnya"
placeholder="0" placeholder="0"
iconLeft="Rp." iconLeft="Rp."
value={displayJumlah}
onChangeText={(value) => handleChangeCurrency(value)}
/> />
<TextCustom size="small" color="gray"> <TextCustom size="small" color="gray">
Minimal donasi Rp. 10.000 Minimal donasi Rp. 10.000

View File

@@ -5,24 +5,93 @@ import {
ViewWrapper, ViewWrapper,
} from "@/components"; } from "@/components";
import { RadioCustom, RadioGroup } from "@/components/Radio/RadioCustom"; import { RadioCustom, RadioGroup } from "@/components/Radio/RadioCustom";
import { LOCAL_STORAGE_KEY } from "@/constants/local-storage-key";
import { useAuth } from "@/hooks/use-auth";
import { dummyMasterBank } from "@/lib/dummy-data/_master/bank"; import { dummyMasterBank } from "@/lib/dummy-data/_master/bank";
import { router, useLocalSearchParams } from "expo-router"; import { apiDonationCreateInvoice } from "@/service/api-client/api-donation";
import { useState } from "react"; import { apiMasterBank } from "@/service/api-client/api-master";
import AsyncStorage from "@react-native-async-storage/async-storage";
import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
import _ from "lodash";
import { useCallback, useEffect, useState } from "react";
export default function DonationSelectBank() { export default function DonationSelectBank() {
const { id, transaction } = useLocalSearchParams(); const { user } = useAuth();
const [value, setValue] = useState<any | number>(""); const { id } = useLocalSearchParams();
console.log("id", id);
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 dataStorage = await AsyncStorage.getItem(
LOCAL_STORAGE_KEY.transactionDonation
);
if (dataStorage) {
const storage = JSON.parse(dataStorage);
const newData = {
...storage,
bankId: select,
authorId: user?.id,
};
console.log("[NEW DATA]", newData);
const response = await apiDonationCreateInvoice({
id: id as string,
data: newData,
});
console.log("[RESPONSE CREATE>>]", response);
if (response.success) {
const invoiceId = response.data.id;
const delStorage = await AsyncStorage.removeItem(
LOCAL_STORAGE_KEY.transactionDonation
);
console.log("[DEL STORAGE]", delStorage);
router.replace(
`/(application)/(user)/donation/[id]/(transaction-flow)/${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
onPress={() => isLoading={isLoading}
router.replace( disabled={!select}
`/(application)/(user)/donation/${id}/(transaction-flow)/${transaction}/invoice` onPress={() => handlerSubmit()}
)
}
> >
Pilih Pilih
</ButtonCustom> </ButtonCustom>
@@ -32,12 +101,14 @@ export default function DonationSelectBank() {
}; };
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, index: number) => (
</BaseBox> <BaseBox key={index}>
))} <RadioCustom label={item.namaBank} value={item.id} />
</BaseBox>
))}
</RadioGroup> </RadioGroup>
</ViewWrapper> </ViewWrapper>
); );

View File

@@ -1,15 +1,13 @@
/* eslint-disable react-hooks/exhaustive-deps */ /* eslint-disable react-hooks/exhaustive-deps */
import { import {
AvatarComp, AvatarComp,
AvatarCustom,
BaseBox, BaseBox,
ButtonCustom, ButtonCustom,
CenterCustom,
Grid, Grid,
LoaderCustom, LoaderCustom,
Spacing, Spacing,
TextCustom, TextCustom,
ViewWrapper, ViewWrapper
} from "@/components"; } from "@/components";
import Donation_BoxPublish from "@/screens/Donation/BoxPublish"; import Donation_BoxPublish from "@/screens/Donation/BoxPublish";
import { apiDonationFundrising } from "@/service/api-client/api-donation"; import { apiDonationFundrising } from "@/service/api-client/api-donation";

View File

@@ -103,20 +103,63 @@ export async function apiDonationUpdateData({
} }
} }
export async function apiDonationGetAll() { export async function apiDonationGetAll({
category,
authorId,
}: {
category: "beranda" | "my-donation";
authorId?: string;
}) {
const authorQuery = authorId ? `&authorId=${authorId}` : "";
try { try {
const response = await apiConfig.get(`/mobile/donation`); const response = await apiConfig.get(
`/mobile/donation?category=${category}${authorQuery}`
);
return response.data; return response.data;
} catch (error) { } catch (error) {
throw error; throw error;
} }
} }
export async function apiDonationFundrising({id}: {id: string}) { export async function apiDonationFundrising({ id }: { id: string }) {
try { try {
const response = await apiConfig.get(`/mobile/donation/${id}/fundrising`); const response = await apiConfig.get(`/mobile/donation/${id}/fundrising`);
return response.data; return response.data;
} catch (error) { } catch (error) {
throw error; throw error;
} }
}
export async function apiDonationCreateDonatur({
id,
data,
}: {
id: string;
data: any;
}) {
try {
const response = await apiConfig.post(`/mobile/donation/${id}/donatur`, {
data: data,
});
return response.data;
} catch (error) {
throw error;
}
}
export async function apiDonationCreateInvoice({
id,
data,
}: {
id: string;
data: any;
}) {
try {
const response = await apiConfig.post(`/mobile/donation/${id}/invoice`, {
data: data,
});
return response.data;
} catch (error) {
throw error;
}
} }