Compare commits

..

5 Commits

Author SHA1 Message Date
ec49999f99 Integrasi API:
Add:
-  hipmi-note.md

Fix:
- app/(application)/(user)/donation/[id]/(transaction-flow)/[invoiceId]/failed.tsx
- app/(application)/(user)/donation/[id]/(transaction-flow)/[invoiceId]/success.tsx
- app/(application)/(user)/event/[id]/confirmation.tsx
- app/(application)/(user)/investment/(tabs)/index.tsx
- app/(application)/(user)/investment/(tabs)/my-holding.tsx
- app/(application)/(user)/investment/[id]/(my-holding)/[id].tsx
- app/(application)/(user)/investment/[id]/(transaction-flow)/failed.tsx
- app/(application)/(user)/investment/[id]/(transaction-flow)/index.tsx
- app/(application)/(user)/investment/[id]/(transaction-flow)/success.tsx
- app/(application)/(user)/investment/[id]/investor.tsx
- app/(application)/admin/investment/[id]/[status]/index.tsx
- app/(application)/admin/investment/[id]/[status]/transaction-detail.tsx
- app/(application)/admin/investment/[id]/list-of-investor.tsx
- lib/dummy-data/investment/dummy-data-not-publish.ts
- screens/Authentication/VerificationView.tsx
- screens/Home/bottomFeatureSection.tsx
- service/api-client/api-investment.ts

### No Issue
2025-11-04 12:13:49 +08:00
f9f996f195 Admin : Investasi integarsi API
### NO Issue
2025-11-03 11:41:31 +08:00
4625831377 Integrasi API: Admin Investasi
Fix:
- app/(application)/(user)/investment/(tabs)/index.tsx
- app/(application)/admin/investment/[id]/[status]/transaction-detail.tsx
- app/(application)/admin/investment/[id]/list-of-investor.tsx
- screens/Invesment/BoxBerandaSection.tsx
- screens/Invesment/DetailDataPublishSection.tsx
- service/api-admin/api-admin-investment.ts

### No Issue
2025-10-30 17:36:42 +08:00
ebd6107c36 Integrasi API: Investment:
Add:
- screens/Invesment/BoxBerandaSection.tsx

Fix:
- app/(application)/(user)/investment/(tabs)/index.tsx
- screens/Donation/BoxPublish.tsx
- screens/Invesment/BoxProgressSection.tsx
- screens/Invesment/DetailDataPublishSection.tsx

### No Issue
2025-10-30 16:38:24 +08:00
f23cfe1107 Integrasi API: Investment & Admin Investment
Add:
- components/_ShareComponent/NoDataText.tsx
- service/api-admin/api-admin-investment.ts

Fix:
- app/(application)/(user)/investment/(tabs)/index.tsx
- app/(application)/admin/investment/[id]/[status]/index.tsx
- app/(application)/admin/investment/[id]/reject-input.tsx
- app/(application)/admin/investment/[status]/status.tsx
- app/(application)/admin/investment/index.tsx
- screens/Invesment/DetailDataPublishSection.tsx

### No Issue
2025-10-30 15:13:33 +08:00
28 changed files with 1515 additions and 530 deletions

View File

@@ -1,3 +1,4 @@
/* eslint-disable react-hooks/exhaustive-deps */
import {
BaseBox,
Grid,
@@ -7,11 +8,60 @@ import {
ViewWrapper,
} from "@/components";
import { MainColor } from "@/constants/color-palet";
import { apiDonationGetInvoiceById } from "@/service/api-client/api-donation";
import { GStyles } from "@/styles/global-styles";
import { dateTimeView } from "@/utils/dateTimeView";
import { formatCurrencyDisplay } from "@/utils/formatCurrencyDisplay";
import { FontAwesome6 } from "@expo/vector-icons";
import dayjs from "dayjs";
import { useFocusEffect, useLocalSearchParams } from "expo-router";
import { useCallback, useState } from "react";
export default function DonasiFailed() {
const { id, invoiceId } = useLocalSearchParams();
const [data, setData] = useState<any>(null);
useFocusEffect(
useCallback(() => {
onLoadData();
}, [id, invoiceId])
);
const onLoadData = async () => {
try {
const response = await apiDonationGetInvoiceById({
id: invoiceId as string,
});
console.log("[DATA]", JSON.stringify(response.data, null, 2));
setData(response.data);
} catch (error) {
console.log("[ERROR]", error);
}
};
const listData = [
{
label: "Bank",
value: (data && data?.MasterBank?.namaBank) || "-",
},
{
label: "Rekening Penerima",
value: (data && data?.MasterBank?.namaAkun) || "-",
},
{
label: "No Rekening",
value: (data && data?.MasterBank?.norek) || "-",
},
{
label: "Jumlah Donasi",
value: (data && formatCurrencyDisplay(data?.nominal)) || "-",
},
{
label: "Tanggal",
value: (data && dateTimeView({ date: data?.createdAt })) || "-",
},
];
return (
<ViewWrapper>
<StackCustom>
@@ -58,26 +108,3 @@ export default function DonasiFailed() {
</ViewWrapper>
);
}
const listData = [
{
label: "Bank",
value: " BCA",
},
{
label: "Rekening Penerima",
value: "Himpunan Pengusaha Muda Indonesia",
},
{
label: "No Rekening",
value: "2304235678854332",
},
{
label: "Jumlah Donasi",
value: "Rp. 750.000",
},
{
label: "Tanggal",
value: `${dayjs(new Date()).format("DD/MM/YYYY")}`,
},
];

View File

@@ -1,3 +1,4 @@
/* eslint-disable react-hooks/exhaustive-deps */
import {
BaseBox,
Grid,
@@ -7,11 +8,60 @@ import {
ViewWrapper,
} from "@/components";
import { MainColor } from "@/constants/color-palet";
import { apiDonationGetInvoiceById } from "@/service/api-client/api-donation";
import { GStyles } from "@/styles/global-styles";
import { dateTimeView } from "@/utils/dateTimeView";
import { formatCurrencyDisplay } from "@/utils/formatCurrencyDisplay";
import { FontAwesome6 } from "@expo/vector-icons";
import dayjs from "dayjs";
import { useFocusEffect, useLocalSearchParams } from "expo-router";
import { useCallback, useState } from "react";
export default function DonationSuccess() {
const { id, invoiceId } = useLocalSearchParams();
const [data, setData] = useState<any>(null);
useFocusEffect(
useCallback(() => {
onLoadData();
}, [id, invoiceId])
);
const onLoadData = async () => {
try {
const response = await apiDonationGetInvoiceById({
id: invoiceId as string,
});
console.log("[DATA]", JSON.stringify(response.data, null, 2));
setData(response.data);
} catch (error) {
console.log("[ERROR]", error);
}
};
const listData = [
{
label: "Bank",
value: (data && data?.MasterBank?.namaBank) || "-",
},
{
label: "Rekening Penerima",
value: (data && data?.MasterBank?.namaAkun) || "-",
},
{
label: "No Rekening",
value: (data && data?.MasterBank?.norek) || "-",
},
{
label: "Jumlah Donasi",
value: (data && formatCurrencyDisplay(data?.nominal)) || "-",
},
{
label: "Tanggal",
value: (data && dateTimeView({ date: data?.createdAt })) || "-",
},
];
return (
<ViewWrapper>
<StackCustom>
@@ -59,25 +109,4 @@ export default function DonationSuccess() {
);
}
const listData = [
{
label: "Bank",
value: " BCA",
},
{
label: "Rekening Penerima",
value: "Himpunan Pengusaha Muda Indonesia",
},
{
label: "No Rekening",
value: "2304235678854332",
},
{
label: "Jumlah Donasi",
value: "Rp. 750.000",
},
{
label: "Tanggal",
value: `${dayjs(new Date()).format("DD/MM/YYYY")}`,
},
];

View File

@@ -261,17 +261,17 @@ export default function UserEventConfirmation() {
<Stack.Screen
options={{
title: "Konfirmasi Event",
headerLeft: () => (
<Ionicons
name="arrow-back"
size={20}
color={MainColor.yellow}
onPress={() =>
router.navigate("/(application)/(user)/event/create")
}
/>
),
}}
// headerLeft: () => (
// <Ionicons
// name="arrow-back"
// size={20}
// color={MainColor.yellow}
// onPress={() =>
// router.navigate("/(application)/(user)/event/create")
// }
// />
// ),
}}
/>
<ViewWrapper>{handlerReturn()}</ViewWrapper>
</>

View File

@@ -1,23 +1,14 @@
import {
BaseBox,
FloatingButton,
Grid,
LoaderCustom,
ProgressCustom,
StackCustom,
TextCustom,
ViewWrapper,
ViewWrapper
} from "@/components";
import API_STRORAGE from "@/constants/base-url-api-strorage";
import DUMMY_IMAGE from "@/constants/dummy-image-value";
import NoDataText from "@/components/_ShareComponent/NoDataText";
import Investment_BoxBerandaSection from "@/screens/Invesment/BoxBerandaSection";
import { apiInvestmentGetAll } from "@/service/api-client/api-investment";
import { Ionicons } from "@expo/vector-icons";
import dayjs from "dayjs";
import { Image } from "expo-image";
import { router, useFocusEffect } from "expo-router";
import _ from "lodash";
import { useCallback, useState } from "react";
import { View } from "react-native";
export default function InvestmentBursa() {
const [list, setList] = useState<any[] | null>(null);
@@ -32,8 +23,10 @@ export default function InvestmentBursa() {
const onLoadList = async () => {
try {
setLoadingList(true);
const response = await apiInvestmentGetAll();
console.log("[DATA LIST]", JSON.stringify(response.data, null, 2));
const response = await apiInvestmentGetAll({
category: "bursa"
});
// console.log("[DATA LIST]", JSON.stringify(response.data, null, 2));
setList(response.data);
} catch (error) {
console.log("[ERROR]", error);
@@ -52,95 +45,12 @@ export default function InvestmentBursa() {
{loadingList ? (
<LoaderCustom />
) : _.isEmpty(list) ? (
<TextCustom>Belum ada data</TextCustom>
<NoDataText />
) : (
list?.map((item: any, index: number) => (
<BaseBox
key={index}
paddingTop={7}
paddingBottom={7}
href={`/investment/${item.id}`}
>
<Grid>
<Grid.Col span={5}>
<Image
source={
item && item.imageId
? API_STRORAGE.GET({ fileId: item.imageId })
: DUMMY_IMAGE.background
}
style={{ width: "auto", height: 100, borderRadius: 10 }}
/>
</Grid.Col>
<Grid.Col span={1}>
<View />
</Grid.Col>
<Grid.Col span={6}>
<StackCustom>
<TextCustom truncate={2}>{item.title}</TextCustom>
<ProgressCustom
label={`${item.progress}%`}
value={item.progress}
size="lg"
/>
{Number(item?.pencarianInvestor) -
dayjs().diff(dayjs(item.countDown), "days") <=
0 ? (
<View
style={{
flexDirection: "row",
alignItems: "center",
gap: 5,
}}
>
<Ionicons
name="alert-circle-outline"
size={16}
color="red"
/>
<TextCustom color="red" size="small">
Periode Investasi Selesai
</TextCustom>
</View>
) : (
<TextCustom>
Sisa waktu:{" "}
{Number(item?.pencarianInvestor) -
dayjs().diff(dayjs(item.countDown), "days")}{" "}
hari
</TextCustom>
)}
</StackCustom>
</Grid.Col>
</Grid>
</BaseBox>
<Investment_BoxBerandaSection id={item.id} data={item} key={index} />
))
)}
</ViewWrapper>
);
}
// <View style={{ padding: 20, gap: 16 }}>
// <TextCustom>Progress 70%</TextCustom>
// <ProgressCustom value={70} color="primary" size="md" />
// <TextCustom>Success Progress</TextCustom>
// <ProgressCustom value={40} color="success" size="lg" />
// <TextCustom>Warning Progress (small)</TextCustom>
// <ProgressCustom value={90} color="warning" size="sm" />
// <TextCustom>Error Indeterminate</TextCustom>
// <ProgressCustom value={null} color="error" size="md" />
// <TextCustom>Custom Radius</TextCustom>
// <ProgressCustom value={60} color="info" size="xl" radius={4} />
// <ProgressCustom value={70} color="primary" size="lg" />
// <ProgressCustom value={45} color="success" size="md" label="Halfway!" />
// <ProgressCustom value={90} color="warning" size="lg" showLabel={false} />
// <ProgressCustom value={null} color="error" size="sm" label="Loading..." />
// </View>;

View File

@@ -1,50 +1,102 @@
/* eslint-disable react-hooks/exhaustive-deps */
import {
BaseBox,
Grid,
LoaderCustom,
ProgressCustom,
Spacing,
StackCustom,
TextCustom,
ViewWrapper,
} from "@/components";
import { router } from "expo-router";
import NoDataText from "@/components/_ShareComponent/NoDataText";
import { useAuth } from "@/hooks/use-auth";
import {
apiInvestmentGetAll
} from "@/service/api-client/api-investment";
import { formatCurrencyDisplay } from "@/utils/formatCurrencyDisplay";
import { router, useFocusEffect } from "expo-router";
import _ from "lodash";
import React, { useCallback, useState } from "react";
import { View } from "react-native";
export default function InvestmentMyHolding() {
const { user } = useAuth();
const [list, setList] = useState<any[] | null>(null);
const [loadingList, setLoadingList] = useState(false);
useFocusEffect(
useCallback(() => {
onLoadList();
}, [user?.id])
);
const onLoadList = async () => {
try {
setLoadingList(true);
const response = await apiInvestmentGetAll({
category: "my-holding",
authorId: user?.id,
});
console.log("[DATA LIST]", JSON.stringify(response.data, null, 2));
setList(response.data);
} catch (error) {
console.log("[ERROR]", error);
} finally {
setLoadingList(false);
}
};
return (
<ViewWrapper hideFooter>
{Array.from({ length: 10 }).map((_, index) => (
<BaseBox key={index} paddingTop={7} paddingBottom={7} onPress={() => router.push(`/investment/${index}/(my-holding)/holding-${index}`)}>
<Grid>
<Grid.Col span={6}>
<StackCustom gap={"xs"}>
<TextCustom truncate={2}>
Title here : 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>
{loadingList ? (
<LoaderCustom />
) : _.isEmpty(list) ? (
<NoDataText />
) : (
list?.map((item, index) => (
<BaseBox
key={index}
paddingTop={7}
paddingBottom={7}
onPress={() =>
router.push(`/investment/${item?.id}/(my-holding)/${item?.id}`)
}
>
<Grid>
<Grid.Col span={6}>
<StackCustom gap={"xs"}>
<TextCustom truncate={2}>{item?.title}</TextCustom>
<Spacing height={5} />
<TextCustom size="small">Rp. 7.500.000</TextCustom>
<TextCustom size="small">300 Lembar</TextCustom>
</StackCustom>
</Grid.Col>
<Grid.Col span={1}>
<View />
</Grid.Col>
<Grid.Col
span={5}
style={{
justifyContent: "center",
alignItems: "center",
}}
>
<ProgressCustom value={(index % 5) * 20} size="lg" />
</Grid.Col>
</Grid>
</BaseBox>
))}
<Spacing height={5} />
<TextCustom size="small">
Rp. {formatCurrencyDisplay(item?.nominal)}
</TextCustom>
<TextCustom size="small">
{item?.lembarTerbeli} Lembar
</TextCustom>
</StackCustom>
</Grid.Col>
<Grid.Col span={1}>
<View />
</Grid.Col>
<Grid.Col
span={5}
style={{
justifyContent: "center",
alignItems: "center",
}}
>
<ProgressCustom
value={item?.progress}
label={`${item?.progress}%`}
size="lg"
/>
</Grid.Col>
</Grid>
</BaseBox>
))
)}
</ViewWrapper>
);
}

View File

@@ -8,6 +8,7 @@ import {
TextCustom,
ViewWrapper,
} from "@/components";
import NoDataText from "@/components/_ShareComponent/NoDataText";
import { useAuth } from "@/hooks/use-auth";
import { apiInvestmentGetInvoice } from "@/service/api-client/api-investment";
import { GStyles } from "@/styles/global-styles";
@@ -74,7 +75,7 @@ export default function InvestmentTransaction() {
{loadList ? (
<LoaderCustom />
) : _.isEmpty(list) ? (
<TextCustom>Tidak ada data</TextCustom>
<NoDataText/>
) : (
list.map((item: any, i: number) => (
<BaseBox

View File

@@ -1,29 +1,56 @@
/* eslint-disable react-hooks/exhaustive-deps */
import {
BackButton,
BaseBox,
DotButton,
DrawerCustom,
Grid,
MenuDrawerDynamicGrid,
StackCustom,
TextCustom,
ViewWrapper,
BackButton,
BaseBox,
DotButton,
DrawerCustom,
Grid,
MenuDrawerDynamicGrid,
StackCustom,
TextCustom,
ViewWrapper,
} from "@/components";
import { IconDocument, IconEdit, IconNews } from "@/components/_Icon";
import { IMenuDrawerItem } from "@/components/_Interface/types";
import { MainColor } from "@/constants/color-palet";
import { ICON_SIZE_MEDIUM } from "@/constants/constans-value";
import { useAuth } from "@/hooks/use-auth";
import Invesment_ComponentBoxOnBottomDetail from "@/screens/Invesment/ComponentBoxOnBottomDetail";
import Invesment_DetailDataPublishSection from "@/screens/Invesment/DetailDataPublishSection";
import { apiInvestmentGetInvoice } from "@/service/api-client/api-investment";
import { formatCurrencyDisplay } from "@/utils/formatCurrencyDisplay";
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 InvestmentDetailHolding() {
const { user } = useAuth();
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 apiInvestmentGetInvoice({
id: id as string,
authorId: user?.id,
category: "invoice",
});
console.log("[DATA]", JSON.stringify(response.data, null, 2));
setData(response.data);
} catch (error) {
console.log("[ERROR]", error);
}
};
const handlePressDraft = (item: IMenuDrawerItem) => {
console.log("PATH >> ", item.path);
@@ -39,7 +66,8 @@ export default function InvestmentDetailHolding() {
const bottomSection = (
<Invesment_ComponentBoxOnBottomDetail
id={id as string}
prospectusId={id as string}
id={data?.Investasi?.id as string}
status={"publish"}
/>
);
@@ -64,10 +92,12 @@ export default function InvestmentDetailHolding() {
<StackCustom gap={"xs"}>
<Grid>
<Grid.Col span={6}>
<TextCustom bold>Nila Transaksi</TextCustom>
<TextCustom bold>Nilai Transaksi</TextCustom>
</Grid.Col>
<Grid.Col span={6}>
<TextCustom bold>Rp. 7.500.000</TextCustom>
<TextCustom bold>
Rp. {data ? formatCurrencyDisplay(data?.nominal) : ""}
</TextCustom>
</Grid.Col>
</Grid>
<Grid>
@@ -75,12 +105,16 @@ export default function InvestmentDetailHolding() {
<TextCustom bold>Saham Terbeli</TextCustom>
</Grid.Col>
<Grid.Col span={6}>
<TextCustom bold>300 Lembar</TextCustom>
<TextCustom bold>
{data ? data?.lembarTerbeli : ""} Lembar
</TextCustom>
</Grid.Col>
</Grid>
</StackCustom>
</BaseBox>
<Invesment_DetailDataPublishSection
data={data && data?.Investasi}
status={"publish"}
bottomSection={bottomSection}
/>

View File

@@ -1,9 +1,73 @@
import { BaseBox, Grid, Spacing, StackCustom, TextCustom, ViewWrapper } from "@/components";
/* eslint-disable react-hooks/exhaustive-deps */
import {
BaseBox,
Grid,
Spacing,
StackCustom,
TextCustom,
ViewWrapper,
} from "@/components";
import { MainColor } from "@/constants/color-palet";
import { apiInvestmentGetInvoice } from "@/service/api-client/api-investment";
import { GStyles } from "@/styles/global-styles";
import { dateTimeView } from "@/utils/dateTimeView";
import { formatCurrencyDisplay } from "@/utils/formatCurrencyDisplay";
import { FontAwesome6 } from "@expo/vector-icons";
import { useLocalSearchParams, useFocusEffect } from "expo-router";
import React from "react";
export default function InvestmentFailed() {
const { id } = useLocalSearchParams();
console.log("[ID]", id);
const [data, setData] = React.useState<any | null>(null);
useFocusEffect(
React.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 listData = [
{
label: "Bank",
value: (data && data?.MasterBank?.namaBank) || "-",
},
{
label: "Rekening Penerima",
value: (data && data?.MasterBank?.namaAkun) || "-",
},
{
label: "No Rekening",
value: (data && data?.MasterBank?.norek) || "-",
},
{
label: "Jumlah",
value: `Rp ${data && formatCurrencyDisplay(data?.nominal)}` || "-",
},
{
label: "Tanggal",
value: (data && dateTimeView({ date: data?.createdAt })) || "-",
},
{
label: "Lembar Terbeli",
value: (data && formatCurrencyDisplay(data?.lembarTerbeli)) || "-",
},
];
return (
<ViewWrapper>
<StackCustom>
@@ -11,8 +75,7 @@ export default function InvestmentFailed() {
<StackCustom>
<TextCustom bold align="center">
Transaksi anda gagal karena bukti transfer tidak sesuai dengan
data kami. Jika ini masalah khusus silahkan hubungi pada kontak
whatsapp kami.
data kami. Hubungi admin untuk memperbaiki masalah ini.
</TextCustom>
<FontAwesome6
@@ -50,30 +113,3 @@ export default function InvestmentFailed() {
</ViewWrapper>
);
}
const listData = [
{
label: "Bank",
value: " BCA",
},
{
label: "Rekening Penerima",
value: "Himpunan Pengusaha Muda Indonesia",
},
{
label: "No Rekening",
value: "2304235678854332",
},
{
label: "Jumlah",
value: "Rp. 1.000.000",
},
{
label: "Tanggal",
value: "2022-01-01",
},
{
label: "Lembar Terbeli",
value: "100",
},
];

View File

@@ -108,7 +108,9 @@ export default function InvestmentInvest() {
<TextCustom>Sisa Lembar Saham</TextCustom>
</Grid.Col>
<Grid.Col span={6} style={{ alignItems: "flex-end" }}>
<TextCustom>{data?.sisaLembar || "-"}</TextCustom>
<TextCustom>
{data && formatCurrencyDisplay(data?.sisaLembar) || "-"}
</TextCustom>
</Grid.Col>
</Grid>
<Grid>
@@ -116,7 +118,9 @@ export default function InvestmentInvest() {
<TextCustom>Harga Per Lembar</TextCustom>
</Grid.Col>
<Grid.Col span={6} style={{ alignItems: "flex-end" }}>
<TextCustom>{data?.hargaLembar || "-"}</TextCustom>
<TextCustom>
{data && formatCurrencyDisplay(data?.hargaLembar) || "-"}
</TextCustom>
</Grid.Col>
</Grid>
<Grid>

View File

@@ -1,3 +1,4 @@
/* eslint-disable react-hooks/exhaustive-deps */
import {
BaseBox,
Grid,
@@ -7,10 +8,66 @@ import {
ViewWrapper,
} from "@/components";
import { MainColor } from "@/constants/color-palet";
import { apiInvestmentGetInvoice } from "@/service/api-client/api-investment";
import { GStyles } from "@/styles/global-styles";
import { dateTimeView } from "@/utils/dateTimeView";
import { formatCurrencyDisplay } from "@/utils/formatCurrencyDisplay";
import { FontAwesome6 } from "@expo/vector-icons";
import { useFocusEffect, useLocalSearchParams } from "expo-router";
import React from "react";
export default function InvestmentSuccess() {
const { id } = useLocalSearchParams();
console.log("[ID]", id);
const [data, setData] = React.useState<any | null>(null);
useFocusEffect(
React.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 listData = [
{
label: "Bank",
value: (data && data?.MasterBank?.namaBank) || "-",
},
{
label: "Rekening Penerima",
value: (data && data?.MasterBank?.namaAkun) || "-",
},
{
label: "No Rekening",
value: (data && data?.MasterBank?.norek) || "-",
},
{
label: "Jumlah",
value: `Rp ${data && formatCurrencyDisplay(data?.nominal)}` || "-",
},
{
label: "Tanggal",
value: (data && dateTimeView({ date: data?.createdAt })) || "-",
},
{
label: "Lembar Terbeli",
value: (data && formatCurrencyDisplay(data?.lembarTerbeli)) || "-",
},
];
return (
<ViewWrapper>
<StackCustom>
@@ -35,8 +92,7 @@ export default function InvestmentSuccess() {
Detail Transaksi
</TextCustom>
<Spacing/>
<Spacing />
<StackCustom>
{listData.map((item, i) => (
@@ -45,7 +101,9 @@ export default function InvestmentSuccess() {
<TextCustom bold>{item.label}</TextCustom>
</Grid.Col>
<Grid.Col span={7}>
<TextCustom style={{paddingLeft: 10}}>{item.value}</TextCustom>
<TextCustom style={{ paddingLeft: 10 }}>
{item.value}
</TextCustom>
</Grid.Col>
</Grid>
))}
@@ -55,30 +113,3 @@ export default function InvestmentSuccess() {
</ViewWrapper>
);
}
const listData = [
{
label: "Bank",
value: " BCA",
},
{
label: "Rekening Penerima",
value: "Himpunan Pengusaha Muda Indonesia",
},
{
label: "No Rekening",
value: "2304235678854332",
},
{
label: "Jumlah",
value: "Rp. 1.000.000",
},
{
label: "Tanggal",
value: "2022-01-01",
},
{
label: "Lembar Terbeli",
value: "100",
},
];

View File

@@ -1,20 +1,66 @@
/* eslint-disable react-hooks/exhaustive-deps */
import {
AvatarUsernameAndOtherComponent,
BoxWithHeaderSection,
LoaderCustom,
TextCustom,
ViewWrapper,
} from "@/components";
import NoDataText from "@/components/_ShareComponent/NoDataText";
import { apiInvestmentGetInvestorById } from "@/service/api-client/api-investment";
import { formatCurrencyDisplay } from "@/utils/formatCurrencyDisplay";
import { useFocusEffect, useLocalSearchParams } from "expo-router";
import _ from "lodash";
import { useCallback, useState } from "react";
export default function InvestmentInvestor() {
const { id } = useLocalSearchParams();
const [list, setList] = useState<any[] | null>(null);
const [loadingList, setLoadingList] = useState(false);
useFocusEffect(
useCallback(() => {
onLoadList();
}, [id])
);
const onLoadList = async () => {
try {
setLoadingList(true);
const response = await apiInvestmentGetInvestorById({
id: id as string,
})
console.log("[DATA LIST]", JSON.stringify(response.data, null, 2));
setList(response.data);
} catch (error) {
console.log("[ERROR]", error);
} finally {
setLoadingList(false);
}
}
return (
<>
<ViewWrapper>
{Array.from({ length: 10 }).map((_, index) => (
<BoxWithHeaderSection key={index}>
<AvatarUsernameAndOtherComponent />
<TextCustom bold>Rp. 7.000.000</TextCustom>
</BoxWithHeaderSection>
))}
{loadingList ? (
<LoaderCustom />
) : _.isEmpty(list) ? (
<NoDataText />
) : (
list?.map((item: any, index: number) => (
<BoxWithHeaderSection key={index}>
<AvatarUsernameAndOtherComponent
avatar={item?.Author?.Profile?.imageId}
name={item?.Author?.username}
avatarHref={`/profile/${item?.Author?.Profile?.id}`}
/>
<TextCustom bold>
Rp. {formatCurrencyDisplay(item?.nominal)}
</TextCustom>
</BoxWithHeaderSection>
))
)}
</ViewWrapper>
</>
);

View File

@@ -1,3 +1,4 @@
/* eslint-disable react-hooks/exhaustive-deps */
import {
ActionIcon,
AlertDefaultSystem,
@@ -11,7 +12,7 @@ import {
Spacing,
StackCustom,
TextCustom,
ViewWrapper
ViewWrapper,
} from "@/components";
import { IconProspectus } from "@/components/_Icon";
import { IconDot, IconList } from "@/components/_Icon/IconComponent";
@@ -19,75 +20,141 @@ import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButt
import AdminButtonReject from "@/components/_ShareComponent/Admin/ButtonReject";
import AdminButtonReview from "@/components/_ShareComponent/Admin/ButtonReview";
import { GridDetail_4_8 } from "@/components/_ShareComponent/GridDetail_4_8";
import ReportBox from "@/components/Box/ReportBox";
import { MainColor } from "@/constants/color-palet";
import { ICON_SIZE_BUTTON } from "@/constants/constans-value";
import { router, useLocalSearchParams } from "expo-router";
import {
apiAdminInvestasiUpdateByStatus,
apiAdminInvestmentDetailById,
} from "@/service/api-admin/api-admin-investment";
import { colorBadgeStatus } from "@/utils/colorBadge";
import { formatCurrencyDisplay } from "@/utils/formatCurrencyDisplay";
import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
import _ from "lodash";
import React from "react";
import Toast from "react-native-toast-message";
export default function AdminInvestmentDetail() {
const { id, status } = useLocalSearchParams();
const [openDrawer, setOpenDrawer] = React.useState(false);
const colorBadge = () => {
if (status === "publish") {
return MainColor.green;
} else if (status === "review") {
return MainColor.orange;
} else if (status === "reject") {
return MainColor.red;
} else {
return MainColor.placeholder;
const [data, setData] = React.useState<any | null>(null);
const [isLoading, setLoading] = React.useState(false);
useFocusEffect(
React.useCallback(() => {
onLoadData();
}, [id])
);
const onLoadData = async () => {
try {
const response = await apiAdminInvestmentDetailById({ id: id as string });
// console.log("[GETONE INVEST]", JSON.stringify(response, null, 2));
if (response.success) {
setData(response.data);
}
} catch (error) {
console.log(error);
}
};
const listData = [
{
label: "Username",
value: `Bagas Banuna ${id}`,
value: (data && data?.author?.username) || "-",
},
{
label: "Judul",
value: `Donasi Lorem ipsum dolor sit amet, consectetur adipisicing elit.`,
value: (data && data?.title) || "-",
},
{
label: "Status",
value: (
<BadgeCustom color={colorBadge()}>
{_.startCase(status as string)}
</BadgeCustom>
),
value:
data && data?.MasterStatusInvestasi?.name ? (
<BadgeCustom
color={colorBadgeStatus({
status: data?.MasterStatusInvestasi?.name as string,
})}
>
{_.startCase(data?.MasterStatusInvestasi?.name as string)}
</BadgeCustom>
) : (
"-"
),
},
{
label: "Dana Dibutuhkan",
value: "Rp 10.000.000",
value: `Rp. ${
(data && data?.targetDana && formatCurrencyDisplay(data?.targetDana)) ||
"-"
}`,
},
{
label: "Harga Perlembar",
value: "Rp 2500",
value: `Rp. ${
(data &&
data?.hargaLembar &&
formatCurrencyDisplay(data?.hargaLembar)) ||
"-"
}`,
},
{
label: "Total Lembar",
value: "2490 lembar",
value:
(data &&
data?.totalLembar &&
formatCurrencyDisplay(data?.totalLembar)) ||
"-",
},
{
label: "ROI",
value: "4 %",
value: `${(data && data?.roi && data?.roi) || 0} %`,
},
{
label: "Pembagian Deviden",
value: "3 bulan",
value: (data && data?.MasterPembagianDeviden?.name) + " bulan" || "-",
},
{
label: "Jadwal Pembagian",
value: "Selamanya",
value: (data && data?.MasterPeriodeDeviden?.name) || "-",
},
{
label: "Pencarian Investor",
value: "30 Hari",
value: (data && data?.MasterPencarianInvestor?.name) + " hari" || "-",
},
];
const handlerSubmitPublish = async () => {
try {
setLoading(true);
const response = await apiAdminInvestasiUpdateByStatus({
id: id as string,
status: "publish",
data: data,
});
// console.log("[GET ON INVEST]", JSON.stringify(response, null, 2));
if (!response.success) {
Toast.show({
type: "error",
text1: "Gagal mempublikasikan data",
});
return;
}
Toast.show({
type: "success",
text1: "Berhasil mempublikasikan data",
});
router.replace(`/admin/investment/publish/status`);
} catch (error) {
console.log("[ERROR]", error);
} finally {
setLoading(false);
}
};
const rightComponent = (
<ActionIcon
icon={<IconDot size={ICON_SIZE_BUTTON} />}
@@ -109,16 +176,28 @@ export default function AdminInvestmentDetail() {
>
{status === "publish" && (
<BaseBox>
<ProgressCustom size="lg" />
<ProgressCustom
label={data && `${data.progress}%` || "0%"}
value={data && data.progress || 0}
size="lg"
/>
<Spacing />
<StackCustom gap={"xs"}>
<GridDetail_4_8
label={<TextCustom bold>Sisa Saham</TextCustom>}
value={<TextCustom>2490 lembar</TextCustom>}
value={
<TextCustom>
{data && formatCurrencyDisplay(data && data?.sisaLembar)} lembar
</TextCustom>
}
/>
<GridDetail_4_8
label={<TextCustom bold>Validasi Transaksi</TextCustom>}
value={<TextCustom>4 Transaksi</TextCustom>}
value={
<TextCustom>
{data && data?.Investasi_Invoice.length} Proses
</TextCustom>
}
/>
</StackCustom>
</BaseBox>
@@ -126,7 +205,7 @@ export default function AdminInvestmentDetail() {
<BaseBox>
<StackCustom>
<DummyLandscapeImage />
<DummyLandscapeImage imageId={data?.imageId} />
{listData.map((item, i) => (
<GridDetail_4_8
key={i}
@@ -150,7 +229,9 @@ export default function AdminInvestmentDetail() {
/>
}
onPress={() => {
router.push(`/(application)/(file)/${id}`);
router.push(
`/(application)/(file)/${data?.prospektusFileId}`
);
}}
>
Preview
@@ -161,46 +242,66 @@ export default function AdminInvestmentDetail() {
label={<TextCustom bold>File Dokumen</TextCustom>}
value={
<StackCustom>
{Array.from({ length: 5 }).map((_, i) => (
<ButtonCustom
key={i}
iconLeft={
<IconProspectus
size={ICON_SIZE_BUTTON}
color={MainColor.darkblue}
/>
}
onPress={() => {
router.push(`/(application)/(file)/${id}`);
}}
>
Dokumen {i + 1}
</ButtonCustom>
))}
{_.isEmpty(data?.DokumenInvestasi) ? (
<TextCustom align="center">-</TextCustom>
) : (
data?.DokumenInvestasi?.map((item: any, index: number) => {
const titleFix = item?.title?.substring(0, 10) || "";
return (
<ButtonCustom
key={item.id || index} // ✅ pastikan key unik
iconLeft={
<IconProspectus
size={ICON_SIZE_BUTTON}
color={MainColor.darkblue}
/>
}
onPress={() => {
router.push(
`/(application)/(file)/${item?.fileId}`
);
}}
>
<TextCustom color="black" truncate>
{titleFix}...
</TextCustom>
</ButtonCustom>
);
})
)}
</StackCustom>
}
/>
</StackCustom>
</BaseBox>
{data &&
data?.catatan &&
(status === "review" || status === "reject") && (
<ReportBox text={data?.catatan} />
)}
{status === "review" && (
<AdminButtonReview
isLoading={isLoading}
onPublish={() => {
AlertDefaultSystem({
title: "Publish",
message: "Apakah anda yakin ingin mempublikasikan data ini?",
textLeft: "Batal",
textRight: "Ya",
onPressLeft: () => {
router.back();
},
onPressRight: () => {
router.back();
handlerSubmitPublish();
},
});
}}
onReject={() => {
router.push(`/admin/investment/${id}/reject-input`);
router.push(
`/admin/investment/${id}/reject-input?status=${_.lowerCase(
data?.MasterStatusInvestasi?.name
)}`
);
}}
/>
)}

View File

@@ -1,67 +1,226 @@
/* eslint-disable react-hooks/exhaustive-deps */
import {
AlertDefaultSystem,
BadgeCustom,
BaseBox,
BoxButtonOnFooter,
ButtonCustom,
Spacing,
StackCustom,
TextCustom,
ViewWrapper,
} from "@/components";
import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButtonAntTitle";
import { GridDetail_4_8 } from "@/components/_ShareComponent/GridDetail_4_8";
import GridTwoView from "@/components/_ShareComponent/GridTwoView";
import { MainColor } from "@/constants/color-palet";
import dayjs from "dayjs";
import { router, useLocalSearchParams } from "expo-router";
import {
apiAdminInvestmentGetOneInvoiceById,
apiAdminInvestmentUpdateInvoice,
} from "@/service/api-admin/api-admin-investment";
import { colorBadgeTransaction } from "@/utils/colorBadge";
import { dateTimeView } from "@/utils/dateTimeView";
import { formatCurrencyDisplay } from "@/utils/formatCurrencyDisplay";
import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
import { useCallback, useState } from "react";
import Toast from "react-native-toast-message";
export default function AdminInvestmentTransactionDetail() {
const { id } = useLocalSearchParams();
const [data, setData] = useState<any | null>(null);
const [isLoading, setLoading] = useState<boolean>(false);
const buttonAction = (
<BoxButtonOnFooter>
<ButtonCustom onPress={() => router.back()}>Terima</ButtonCustom>
</BoxButtonOnFooter>
useFocusEffect(
useCallback(() => {
onLoadData();
}, [id])
);
const onLoadData = async () => {
try {
const response = await apiAdminInvestmentGetOneInvoiceById({
id: id as string,
});
// console.log("[RESPONSE]", JSON.stringify(response, null, 2));
if (response.success) {
setData(response.data);
}
} catch (error) {
console.log("[ERROR]", error);
}
};
const listData = [
{
label: "Investor",
value: "Bagas Banuna",
value: (data && data?.Author?.username) || "-",
},
{
label: "Bank",
value: "BCA",
value: (data && data?.MasterBank?.namaBank) || "-",
},
{
label: "Jumlah Investasi",
value: "Rp. 1.000.000",
value: (data && `Rp. ${formatCurrencyDisplay(data?.nominal)}`) || "-",
},
{
label: "Lembar terbeli",
value: (data && formatCurrencyDisplay(data?.lembarTerbeli)) || "-",
},
{
label: "Status",
value: <BadgeCustom color={MainColor.green}>Berhasil</BadgeCustom>,
value:
data && data?.StatusInvoice?.name ? (
<BadgeCustom
color={colorBadgeTransaction({
status: data?.StatusInvoice?.name,
})}
>
{data?.StatusInvoice?.name}
</BadgeCustom>
) : (
"-"
),
},
{
label: "Tanggal",
value: dayjs().format("DD-MM-YYYY HH:mm:ss"),
value: (data && dateTimeView({ date: data?.createdAt })) || "-",
},
{
label: "Bukti Transfer",
value: (
<ButtonCustom
onPress={() =>
router.push(`/(application)/(image)/preview-image/${id}`)
}
>
Cek
</ButtonCustom>
),
value:
data && data?.imageId ? (
<ButtonCustom
onPress={() =>
router.push(
`/(application)/(image)/preview-image/${data?.imageId}`
)
}
>
Cek
</ButtonCustom>
) : (
"-"
),
},
];
const handlerSubmit = async ({
category,
}: {
category: "accept" | "deny";
}) => {
try {
setLoading(true);
const response = await apiAdminInvestmentUpdateInvoice({
id: id as string,
category: category,
data: {
investasiId: data?.investasiId,
lembarTerbeli: data?.lembarTerbeli,
},
});
// console.log("[RESPONSE SUBMIT]", JSON.stringify(response, null, 2));
if (!response.success) {
Toast.show({
type: "error",
text1: "Gagal update status transaksi",
});
return;
}
Toast.show({
type: "success",
text1: "Berhasil update status transaksi",
});
router.back();
} catch (error) {
console.log("[ERROR]", error);
} finally {
setLoading(false);
}
};
const buttonAction = () => {
if (data?.StatusInvoice?.name === "Proses") {
return (
<GridTwoView
spanLeft={6}
spanRight={6}
styleLeft={{ paddingRight: 10 }}
styleRight={{ paddingLeft: 10 }}
leftIcon={
<ButtonCustom
isLoading={isLoading}
backgroundColor={MainColor.red}
textColor="white"
onPress={() => {
AlertDefaultSystem({
title: "Konfirmasi transaksi",
message: "Apakah anda yakin ingin menolak transaksi ini?",
textLeft: "Tidak",
textRight: "Ya",
onPressRight: () => {
handlerSubmit({
category: "deny",
});
},
});
}}
>
Tolak
</ButtonCustom>
}
rightIcon={
<ButtonCustom
isLoading={isLoading}
onPress={() => {
AlertDefaultSystem({
title: "Konfirmasi transaksi",
message: "Apakah anda yakin ingin menyetujui transaksi ini?",
textLeft: "Tidak",
textRight: "Ya",
onPressRight: () => {
handlerSubmit({
category: "accept",
});
},
});
}}
>
Terima
</ButtonCustom>
}
/>
);
} else if (data?.StatusInvoice?.name === "Gagal") {
return (
<>
<ButtonCustom textColor="red" onPress={() => router.back()}>
Gagal
</ButtonCustom>
</>
);
} else {
return (
<>
<ButtonCustom disabled={true}>
Status: {data?.StatusInvoice?.name}
</ButtonCustom>
</>
);
}
};
return (
<>
<ViewWrapper
headerComponent={<AdminBackButtonAntTitle title="Detail Transaksi Investor" />}
footerComponent={buttonAction}
headerComponent={
<AdminBackButtonAntTitle title="Detail Transaksi Investor" />
}
// footerComponent={buttonAction()}
>
<BaseBox>
<StackCustom>
@@ -74,6 +233,8 @@ export default function AdminInvestmentTransactionDetail() {
))}
</StackCustom>
</BaseBox>
<Spacing />
{buttonAction()}
</ViewWrapper>
</>
);

View File

@@ -1,7 +1,9 @@
/* eslint-disable react-hooks/exhaustive-deps */
import {
ActionIcon,
BadgeCustom,
CenterCustom,
LoaderCustom,
SelectCustom,
StackCustom,
TextCustom,
@@ -10,23 +12,98 @@ import {
import { IconView } from "@/components/_Icon/IconComponent";
import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButtonAntTitle";
import { GridViewCustomSpan } from "@/components/_ShareComponent/GridViewCustomSpan";
import { MainColor } from "@/constants/color-palet";
import NoDataText from "@/components/_ShareComponent/NoDataText";
import { ICON_SIZE_BUTTON } from "@/constants/constans-value";
import { dummyMasterStatusTransaction } from "@/lib/dummy-data/_master/status-transaction";
import { router, useLocalSearchParams } from "expo-router";
import React from "react";
import { apiAdminInvestmentListOfInvestor } from "@/service/api-admin/api-admin-investment";
import { apiMasterTransaction } from "@/service/api-client/api-master";
import { colorBadgeTransaction } from "@/utils/colorBadge";
import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
import _ from "lodash";
import React, { useEffect } from "react";
import { View } from "react-native";
import { Divider } from "react-native-paper";
export default function AdminInvestmentListOfInvestor() {
const { id } = useLocalSearchParams();
console.log("[ID]", id);
const [listData, setListData] = React.useState<any[] | null>(null);
const [loadData, setLoadData] = React.useState(false);
const [master, setMaster] = React.useState<any[]>([]);
const [selectValue, setSelectValue] = React.useState<string | null>(null);
const [selectedStatus, setSelectedStatus] = React.useState<string | null>(
null
);
useEffect(() => {
onLoadMaster();
}, []);
const onLoadMaster = async () => {
try {
const response = await apiMasterTransaction();
if (response.success) {
setMaster(response.data);
}
} catch (error) {
console.log("[ERROR]", error);
setMaster([]);
}
};
useFocusEffect(
React.useCallback(() => {
onLoadData();
}, [id, selectValue])
);
const onLoadData = async () => {
try {
setLoadData(true);
const response = await apiAdminInvestmentListOfInvestor({
id: id as string,
status: selectedStatus as any,
});
console.log("[LIST OF INVESTOR]", JSON.stringify(response, null, 2));
if (response.success) {
setListData(response.data);
}
} catch (error) {
console.log("[ERROR]", error);
setListData([]);
} finally {
setLoadData(false);
}
};
useEffect(() => {
onLoadMaster();
}, []);
const searchComponent = (
<View style={{ flexDirection: "row", gap: 5 }}>
<SelectCustom
placeholder="Pilih status transaksi"
data={dummyMasterStatusTransaction}
onChange={(value) => console.log(value)}
data={
_.isEmpty(master)
? []
: master?.map((item: any) => ({
label: item.name,
value: item.id,
}))
}
value={selectValue}
onChange={(value: any) => {
setSelectValue(value);
const nameSelected = master.find((item: any) => item.id === value);
const statusChooses = _.lowerCase(nameSelected?.name);
setSelectedStatus(statusChooses);
}}
styleContainer={{ width: "100%", marginBottom: 0 }}
allowClear
/>
</View>
);
@@ -40,66 +117,77 @@ export default function AdminInvestmentListOfInvestor() {
return (
<>
<ViewWrapper
headerComponent={headerComponent}
>
<GridViewCustomSpan
span1={3}
span2={5}
span3={4}
component1={
<TextCustom bold align="center">
Aksi
</TextCustom>
}
component2={
<TextCustom bold align="center">
Investor
</TextCustom>
}
component3={
<TextCustom bold align="center">
Status
</TextCustom>
}
/>
<Divider />
<ViewWrapper headerComponent={headerComponent}>
<StackCustom>
{Array.from({ length: 10 }).map((_, index) => (
<View key={index}>
<GridViewCustomSpan
span1={3}
span2={5}
span3={4}
component1={
<CenterCustom>
<ActionIcon
icon={<IconView size={ICON_SIZE_BUTTON} color="black" />}
onPress={() => {
router.push(
`/admin/investment/${id}/berhasil/transaction-detail`
);
}}
/>
</CenterCustom>
}
component2={
<TextCustom bold align="center" truncate>
Bagas Banuna
</TextCustom>
}
component3={
<BadgeCustom
style={{ alignSelf: "center" }}
color={MainColor.green}
>
Berhasil
</BadgeCustom>
}
/>
<Divider />
</View>
))}
<GridViewCustomSpan
span1={3}
span2={5}
span3={4}
component1={
<TextCustom bold align="center">
Aksi
</TextCustom>
}
component2={
<TextCustom bold align="center">
Investor
</TextCustom>
}
component3={
<TextCustom bold align="center">
Status
</TextCustom>
}
/>
<Divider />
<StackCustom>
{loadData ? (
<LoaderCustom />
) : _.isEmpty(listData) ? (
<NoDataText />
) : (
listData?.map((item: any, index: number) => (
<View key={index}>
<GridViewCustomSpan
span1={3}
span2={5}
span3={4}
component1={
<CenterCustom>
<ActionIcon
icon={
<IconView size={ICON_SIZE_BUTTON} color="black" />
}
onPress={() => {
router.push(
`/admin/investment/${item?.id}/${_.lowerCase(
item?.StatusInvoice?.name
)}/transaction-detail`
);
}}
/>
</CenterCustom>
}
component2={
<TextCustom bold align="center" truncate>
{item?.Author?.username || "-"}
</TextCustom>
}
component3={
<BadgeCustom
style={{ alignSelf: "center" }}
color={colorBadgeTransaction({
status: item?.StatusInvoice?.name,
})}
>
{item?.StatusInvoice?.name}
</BadgeCustom>
}
/>
</View>
))
)}
</StackCustom>
</StackCustom>
</ViewWrapper>
</>

View File

@@ -1,3 +1,4 @@
/* eslint-disable react-hooks/exhaustive-deps */
import {
AlertDefaultSystem,
BoxButtonOnFooter,
@@ -6,15 +7,83 @@ import {
} from "@/components";
import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButtonAntTitle";
import AdminButtonReject from "@/components/_ShareComponent/Admin/ButtonReject";
import { router, useLocalSearchParams } from "expo-router";
import { useState } from "react";
import { apiAdminInvestasiUpdateByStatus, apiAdminInvestmentDetailById } from "@/service/api-admin/api-admin-investment";
import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
import { useCallback, useState } from "react";
import Toast from "react-native-toast-message";
export default function AdminInvestmentRejectInput() {
const { id } = useLocalSearchParams();
const [value, setValue] = useState(id as string);
const { id, status } = useLocalSearchParams();
console.log("[STATUS]", status);
const [value, setValue] = useState<any | null>(null);
const [isLoading , setLoading] = useState(false)
useFocusEffect(
useCallback(() => {
onLoadData();
}, [id])
);
const onLoadData = async () => {
try {
const response = await apiAdminInvestmentDetailById({ id: id as string });
console.log("[DATA]", JSON.stringify(response, null, 2));
if (response.success) {
setValue(response.data?.catatan);
}
} catch (error) {
console.log(error);
}
};
const handlerSubmit = async () => {
if (!value) {
Toast.show({
type: "error",
text1: "Harap masukan alasan penolakan",
});
return;
}
try {
setLoading(true)
const response = await apiAdminInvestasiUpdateByStatus({
id: id as string,
status: "reject",
data: value,
});
console.log("[RESPONSE]", JSON.stringify(response, null, 2));
if (!response.success) {
Toast.show({
type: "error",
text1: "Gagal melakukan report",
});
return;
}
Toast.show({
type: "success",
text1: "Berhasil melakukan report",
});
if (status === "review") {
router.replace(`/admin/investment/reject/status`);
} else {
router.back();
}
} catch (error) {
console.error(["ERROR"], error);
} finally {
setLoading(false)
}
};
const buttonSubmit = (
<BoxButtonOnFooter>
<AdminButtonReject
isLoading={isLoading}
title="Reject"
onReject={() =>
AlertDefaultSystem({
@@ -22,12 +91,8 @@ export default function AdminInvestmentRejectInput() {
message: "Apakah anda yakin ingin menolak data ini?",
textLeft: "Batal",
textRight: "Ya",
onPressLeft: () => {
router.back();
},
onPressRight: () => {
console.log("value:", value);
router.replace(`/admin/investment/reject/status`);
handlerSubmit();
},
})
}
@@ -39,7 +104,9 @@ export default function AdminInvestmentRejectInput() {
<>
<ViewWrapper
footerComponent={buttonSubmit}
headerComponent={<AdminBackButtonAntTitle title="Penolakan Investasi" />}
headerComponent={
<AdminBackButtonAntTitle title="Penolakan Investasi" />
}
>
<TextAreaCustom
value={value}

View File

@@ -1,69 +1,114 @@
/* eslint-disable react-hooks/exhaustive-deps */
import {
ActionIcon,
SearchInput,
Spacing,
TextCustom,
ViewWrapper
ActionIcon,
LoaderCustom,
SearchInput,
StackCustom,
TextCustom,
ViewWrapper
} from "@/components";
import AdminComp_BoxTitle from "@/components/_ShareComponent/Admin/BoxTitlePage";
import AdminTitleTable from "@/components/_ShareComponent/Admin/TableTitle";
import AdminTableValue from "@/components/_ShareComponent/Admin/TableValue";
import AdminTitlePage from "@/components/_ShareComponent/Admin/TitlePage";
import NoDataText from "@/components/_ShareComponent/NoDataText";
import { ICON_SIZE_BUTTON } from "@/constants/constans-value";
import { apiAdminInvestment } from "@/service/api-admin/api-admin-investment";
import { Octicons } from "@expo/vector-icons";
import { router, useLocalSearchParams } from "expo-router";
import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
import _ from "lodash";
import React, { useCallback } from "react";
import { Divider } from "react-native-paper";
export default function AdminInvestmentStatus() {
const { status } = useLocalSearchParams();
console.log("[STATUS]", status);
const [listData, setListData] = React.useState<any[] | null>(null);
const [loadData, setLoadingData] = React.useState(false);
const [search, setSearch] = React.useState("");
useFocusEffect(
useCallback(() => {
onLoadData();
}, [status, search])
);
const onLoadData = async () => {
try {
setLoadingData(true);
const response = await apiAdminInvestment({
category: status as "publish" | "review" | "reject",
search,
});
console.log("[LIST DATA]", JSON.stringify(response, null, 2));
if (response.success) {
setListData(response.data);
}
} catch (error) {
console.log(error);
setListData([]);
} finally {
setLoadingData(false);
}
};
const rightComponent = (
<SearchInput
containerStyle={{ width: "100%", marginBottom: 0 }}
placeholder="Cari"
value={search}
onChangeText={setSearch}
/>
);
return (
<>
<ViewWrapper
headerComponent={
<ViewWrapper headerComponent={<AdminTitlePage title="Investasi" />}>
<StackCustom gap={"sm"}>
<AdminComp_BoxTitle
title={`Investasi ${_.startCase(status as string)}`}
title={`${_.startCase(status as string)}`}
rightComponent={rightComponent}
/>
}
>
<AdminTitleTable
title1="Aksi"
title2="Username"
title3="Judul Investasi"
/>
<Spacing />
<Divider />
{Array.from({ length: 10 }).map((_, index) => (
<AdminTableValue
key={index}
value1={
<ActionIcon
icon={
<Octicons name="eye" size={ICON_SIZE_BUTTON} color="black" />
}
onPress={() => {
router.push(`/admin/investment/${index}/${status}`);
}}
/>
}
value2={<TextCustom truncate={1}>Username username</TextCustom>}
value3={
<TextCustom truncate={2}>
Lorem ipsum dolor sit amet consectetur adipisicing elit.
Blanditiis asperiores quidem deleniti architecto eaque et
nostrum, ad consequuntur eveniet quisquam quae voluptatum
ducimus! Dolorem nobis modi officia debitis, beatae mollitia.
</TextCustom>
}
<AdminTitleTable
title1="Aksi"
title2="Username"
title3="Judul Investasi"
/>
))}
<Divider />
{loadData ? (
<LoaderCustom />
) : _.isEmpty(listData) ? (
<NoDataText />
) : (
listData?.map((item: any, index: number) => (
<AdminTableValue
key={index}
value1={
<ActionIcon
icon={
<Octicons
name="eye"
size={ICON_SIZE_BUTTON}
color="black"
/>
}
onPress={() => {
router.push(`/admin/investment/${item.id}/${status}`);
}}
/>
}
value2={<TextCustom truncate={1}>{item?.author?.username}</TextCustom>}
value3={
<TextCustom truncate={2}>
{item?.title}
</TextCustom>
}
/>
))
)}
</StackCustom>
</ViewWrapper>
</>
);

View File

@@ -7,8 +7,51 @@ import {
import AdminComp_BoxDashboard from "@/components/_ShareComponent/Admin/BoxDashboard";
import AdminTitlePage from "@/components/_ShareComponent/Admin/TitlePage";
import { MainColor } from "@/constants/color-palet";
import { apiAdminInvestment } from "@/service/api-admin/api-admin-investment";
import { useFocusEffect } from "expo-router";
import React, { useCallback } from "react";
export default function AdminInvestment() {
const [data, setData] = React.useState<any | null>(null);
useFocusEffect(
useCallback(() => {
onLoadData();
}, [])
);
const onLoadData = async () => {
try {
const response = await apiAdminInvestment({
category: "dashboard",
});
console.log(JSON.stringify(response, null, 2));
if (response.success) {
setData(response.data);
}
} catch (error) {
console.log(error);
}
};
const listData = [
{
label: "Publish",
value: (data && data.publish) || 0,
icon: <IconPublish size={25} color={MainColor.green} />,
},
{
label: "Review",
value: (data && data.review) || 0,
icon: <IconReview size={25} color={MainColor.orange} />,
},
{
label: "Reject",
value: (data && data.reject) || 0,
icon: <IconReject size={25} color={MainColor.red} />,
},
];
return (
<>
<ViewWrapper>
@@ -23,21 +66,3 @@ export default function AdminInvestment() {
</>
);
}
const listData = [
{
label: "Publish",
value: 3,
icon: <IconPublish size={25} color={MainColor.green} />,
},
{
label: "Review",
value: 5,
icon: <IconReview size={25} color={MainColor.orange} />,
},
{
label: "Reject",
value: 8,
icon: <IconReject size={25} color={MainColor.red} />,
},
];

View File

@@ -0,0 +1,9 @@
import TextCustom from "../Text/TextCustom";
export default function NoDataText({ text }: { text?: string }) {
return (
<TextCustom color="gray" align="center" bold size={"small"}>
{text ? text : "Belum ada data"}
</TextCustom>
);
}

9
hipmi-note.md Normal file
View File

@@ -0,0 +1,9 @@
eas build --profile production : for build production on expo with eas
npx expo prebuild : untuk build dan membuat folder android & ios
open ios/<nama-app>.xcworkspace : untuk membuka file xcode
Build ios : bun run ios
Build android : bun run android
Exp: open ios/HIPMIBADUNG.xcworkspace

View File

@@ -36,7 +36,7 @@ const listDataNotPublishInvesment = ({ data }: { data: any }) => [
const listDataPublishInvesment = ({ data }: { data: any }) => [
{
label: "Investor",
value: data?.investor,
value: data?.Investasi_Invoice.length || "-",
},
{
label: "Target Dana",

View File

@@ -10,6 +10,7 @@ import { router, useLocalSearchParams } from "expo-router";
import { useEffect, useState } from "react";
import { Text, View } from "react-native";
import { OtpInput } from "react-native-otp-entry";
import { ActivityIndicator } from "react-native-paper";
import Toast from "react-native-toast-message";
export default function VerificationView() {
@@ -18,22 +19,41 @@ export default function VerificationView() {
const [codeOtp, setCodeOtp] = useState<string>("");
const [inputOtp, setInputOtp] = useState<string>("");
const [userNumber, setUserNumber] = useState<string>("");
const [loading, setLoading] = useState<boolean>(false);
const [recodeOtp, setRecodeOtp] = useState<boolean>(false);
// --- Context ---
const { validateOtp, isLoading } = useAuth();
const { validateOtp, isLoading, loginWithNomor } = useAuth();
useEffect(() => {
onLoadCheckCodeOtp();
}, []);
}, [recodeOtp]);
async function onLoadCheckCodeOtp() {
setRecodeOtp(false);
const kodeId = await AsyncStorage.getItem("kode_otp");
const response = await apiCheckCodeOtp({ kodeId: kodeId as string });
console.log("Response check code otp >>", JSON.stringify(response.otp, null, 2));
console.log(
"Response check code otp >>",
JSON.stringify(response.otp, null, 2)
);
setCodeOtp(response.otp);
setUserNumber(response.nomor);
}
const handlerResendOtp = async () => {
try {
setLoading(true);
await loginWithNomor(nomor as string);
setRecodeOtp(true);
} catch (error) {
console.log("Error check code otp", error);
} finally {
setLoading(false);
}
};
const handleVerification = async () => {
const codeOtpNumber = parseInt(codeOtp);
const inputOtpNumber = parseInt(inputOtp);
@@ -109,17 +129,28 @@ export default function VerificationView() {
onTextChange={(otp: string) => setInputOtp(otp)}
/>
<Spacing height={30} />
<Text style={GStyles.textLabel}>
Tidak menerima kode ?{" "}
<Text style={GStyles.textLabel}>Kirim Ulang</Text>
</Text>
<View style={{ flexDirection: "row", alignItems: "center" }}>
<Text style={GStyles.textLabel}>Tidak menerima kode ? </Text>
{loading ? (
<ActivityIndicator size={10} color={MainColor.yellow} />
) : (
<Text
style={GStyles.textLabel}
onPress={() => {
handlerResendOtp();
}}
>
Kirim Ulang
</Text>
)}
</View>
</View>
<Spacing height={30} />
</View>
<ButtonCustom
isLoading={isLoading}
disabled={codeOtp === ""}
disabled={codeOtp === "" || recodeOtp === true}
backgroundColor={MainColor.yellow}
textColor={MainColor.black}
onPress={() => handleVerification()}

View File

@@ -22,7 +22,7 @@ export default function Donation_BoxPublish({
sisa: 0,
reminder: false,
});
useEffect(() => {
updateCountDown();
}, [data]);

View File

@@ -1,12 +1,35 @@
import { ClickableCustom, TextCustom } from "@/components";
import Spacing from "@/components/_ShareComponent/Spacing";
import React from "react";
import React, { useCallback, useState } from "react";
import { View } from "react-native";
import Icon from "react-native-vector-icons/FontAwesome";
import { stylesHome } from "./homeViewStyle";
import { router } from "expo-router";
import { router, useFocusEffect } from "expo-router";
import { apiJobGetAll } from "@/service/api-client/api-job";
export default function Home_BottomFeatureSection() {
const [listData, setListData] = useState<any>([]);
const onLoadData = async () => {
try {
const response = await apiJobGetAll({
category: "beranda",
});
// console.log("[DATA JOB]", JSON.stringify(response.data, null, 2));
const result = response.data.slice(-2);
setListData(result);
} catch (error) {
console.log("[ERROR]", error);
}
};
useFocusEffect(
useCallback(() => {
onLoadData();
}, [])
);
return (
<>
<ClickableCustom onPress={() => router.push("/job")}>
@@ -21,32 +44,17 @@ export default function Home_BottomFeatureSection() {
<View style={stylesHome.vacancyList}>
{/* Vacancy Item 1 */}
<View style={stylesHome.vacancyItem}>
{/* <Icon name="user" size={20} color="#FFD700" /> */}
<View style={stylesHome.vacancyDetails}>
<TextCustom bold color="yellow" truncate size="large">
Bagas_banuna
</TextCustom>
<Spacing height={5} />
<TextCustom truncate={2}>
Dicari perawat kucing dan perawat anjing
</TextCustom>
{listData.map((item: any, index: number) => (
<View style={stylesHome.vacancyItem} key={index}>
<View style={stylesHome.vacancyDetails}>
<TextCustom bold color="yellow" truncate size="large">
{item.title}
</TextCustom>
<Spacing height={5} />
<TextCustom truncate={2}>{item.deskripsi}</TextCustom>
</View>
</View>
</View>
{/* Vacancy Item 2 */}
<View style={stylesHome.vacancyItem}>
{/* <Icon name="user" size={20} color="#FFD700" /> */}
<View style={stylesHome.vacancyDetails}>
<TextCustom bold color="yellow" truncate size="large">
fibramarcell
</TextCustom>
<Spacing height={5} />
<TextCustom truncate={2}>
Di Butuhkan Seorang Programer dan Designer
</TextCustom>
</View>
</View>
))}
</View>
</View>
</ClickableCustom>

View File

@@ -0,0 +1,96 @@
/* eslint-disable react-hooks/exhaustive-deps */
import {
BaseBox,
Grid,
ProgressCustom,
StackCustom,
TextCustom,
} from "@/components";
import API_STRORAGE from "@/constants/base-url-api-strorage";
import DUMMY_IMAGE from "@/constants/dummy-image-value";
import { countDownAndCondition } from "@/utils/countDownAndCondition";
import { Ionicons } from "@expo/vector-icons";
import { Image } from "expo-image";
import { useEffect, useState } from "react";
import { View } from "react-native";
export default function Investment_BoxBerandaSection({
id,
data,
}: {
id: string;
data: any;
}) {
// console.log("[DATA By one]", JSON.stringify(data, null, 2));
const [value, setValue] = useState({
sisa: 0,
reminder: false,
});
useEffect(() => {
updateCountDown();
}, [data]);
const updateCountDown = () => {
const countDown = countDownAndCondition({
duration: data?.pencarianInvestor,
publishTime: data?.countDown,
});
setValue({
sisa: countDown.durationDay,
reminder: countDown.reminder,
});
};
return (
<>
<BaseBox paddingTop={7} paddingBottom={7} href={`/investment/${id}`}>
<Grid>
<Grid.Col span={5}>
<Image
source={
data && data.imageId
? API_STRORAGE.GET({ fileId: data.imageId })
: DUMMY_IMAGE.background
}
style={{ width: "auto", height: 100, borderRadius: 10 }}
/>
</Grid.Col>
<Grid.Col span={1}>
<View />
</Grid.Col>
<Grid.Col span={6}>
<StackCustom>
<TextCustom truncate={2}>{data.title}</TextCustom>
<ProgressCustom
label={`${data.progress}%`}
value={data.progress}
size="lg"
/>
{value.reminder ? (
<View
style={{
flexDirection: "row",
alignItems: "center",
gap: 5,
}}
>
<Ionicons name="alert-circle-outline" size={16} color="red" />
<TextCustom truncate color="red" size="small">
Periode Investasi Berakhir
</TextCustom>
</View>
) : (
<TextCustom>
Sisa waktu: {value.sisa} hari
</TextCustom>
)}
</StackCustom>
</Grid.Col>
</Grid>
</BaseBox>
</>
);
}

View File

@@ -7,7 +7,7 @@ export default function Invesment_BoxProgressSection({progress, status}: {progre
<BaseBox>
<StackCustom>
<TextCustom bold>Progress Saham</TextCustom>
<ProgressCustom label={progress + "%"} value={progress} size="lg" />
<ProgressCustom label={(progress || 0) + "%"} value={progress || 0} size="lg" />
</StackCustom>
</BaseBox>
)}

View File

@@ -1,9 +1,12 @@
import { Spacing, StackCustom } from "@/components";
/* eslint-disable react-hooks/exhaustive-deps */
import { ButtonCustom, Spacing, StackCustom } from "@/components";
import ReportBox from "@/components/Box/ReportBox";
import {
listDataNotPublishInvesment,
listDataPublishInvesment,
} from "@/lib/dummy-data/investment/dummy-data-not-publish";
import React from "react";
import { countDownAndCondition } from "@/utils/countDownAndCondition";
import React, { useEffect, useState } from "react";
import Invesment_BoxDetailDataSection from "./BoxDetailDataSection";
import Invesment_BoxProgressSection from "./BoxProgressSection";
import Investment_ButtonStatusSection from "./ButtonStatusSection";
@@ -19,11 +22,39 @@ export default function Invesment_DetailDataPublishSection({
bottomSection?: React.ReactNode;
buttonSection?: React.ReactNode;
}) {
const [value, setValue] = useState({
sisa: 0,
reminder: false,
});
useEffect(() => {
updateCountDown();
}, [data]);
const updateCountDown = () => {
const countDown = countDownAndCondition({
duration: data?.durasiDonasi,
publishTime: data?.publishTime,
});
setValue({
sisa: countDown.durationDay,
reminder: countDown.reminder,
});
};
return (
<>
<StackCustom gap={"sm"}>
<Invesment_BoxProgressSection progress={data?.progress} status={status as string} />
{data &&
data?.catatan &&
(status === "draft" || status === "reject") && (
<ReportBox text={data?.catatan} />
)}
<Invesment_BoxProgressSection
progress={data?.progress}
status={status as string}
/>
<Invesment_BoxDetailDataSection
title={data?.title}
author={data?.author}
@@ -35,11 +66,18 @@ export default function Invesment_DetailDataPublishSection({
}
bottomSection={bottomSection}
/>
<Investment_ButtonStatusSection
id={data?.id}
status={status as string}
buttonPublish={buttonSection}
/>
{value.reminder ? (
<ButtonCustom disabled>
Periode Investasi Berakhir
</ButtonCustom>
) : (
<Investment_ButtonStatusSection
id={data?.id}
status={status as string}
buttonPublish={buttonSection}
/>
)}
</StackCustom>
<Spacing />
</>

View File

@@ -0,0 +1,113 @@
import { apiConfig } from "../api-config";
export async function apiAdminInvestment({
category,
search,
}: {
category: "dashboard" | "publish" | "review" | "reject";
search?: string;
}) {
const propsQuery =
category === "dashboard"
? `category=${category}`
: `category=${category}&search=${search}`;
try {
const response = await apiConfig.get(
`/mobile/admin/investment?${propsQuery}`
);
return response.data;
} catch (error) {
throw error;
}
}
export async function apiAdminInvestmentDetailById({ id }: { id: string }) {
try {
const response = await apiConfig.get(`/mobile/admin/investment/${id}`);
return response.data;
} catch (error) {
throw error;
}
}
export async function apiAdminInvestasiUpdateByStatus({
id,
status,
data,
}: {
id: string;
status: "publish" | "review" | "reject";
data: any;
}) {
try {
const response = await apiConfig.put(
`/mobile/admin/investment/${id}?status=${status}`,
{
data: data,
}
);
return response.data;
} catch (error) {
throw error;
}
}
export async function apiAdminInvestmentListOfInvestor({
id,
status,
}: {
id: string;
status: "berhasil" | "gagal" | "proses" | "menunggu" | null;
}) {
const query = status && status !== null ? `?status=${status}` : "";
try {
const response = await apiConfig.get(
`/mobile/admin/investment/${id}/investor${query}`
);
return response.data;
} catch (error) {
throw error;
}
}
export async function apiAdminInvestmentGetOneInvoiceById({
id,
}: {
id: string;
}) {
try {
const response = await apiConfig.get(
`/mobile/admin/investment/${id}/invoice`
);
return response.data;
} catch (error) {
throw error;
}
}
export async function apiAdminInvestmentUpdateInvoice({
id,
category,
data,
}: {
id: string;
category: "deny" | "accept";
data: {
investasiId: string;
lembarTerbeli: number;
};
}) {
try {
const response = await apiConfig.put(
`/mobile/admin/investment/${id}/invoice?category=${category}`,
{
data: data,
}
);
return response.data;
} catch (error) {
throw error;
}
}

View File

@@ -128,9 +128,19 @@ export async function apiInvestmentDeleteDocument({ id }: { id: string }) {
}
}
export async function apiInvestmentGetAll() {
export async function apiInvestmentGetAll({
category,
authorId,
}: {
category: "my-holding" | "bursa";
authorId?: string;
}) {
try {
const response = await apiConfig.get(`/mobile/investment`);
const response = await apiConfig.get(
`/mobile/investment?category=${category}${
authorId ? `&authorId=${authorId}` : ""
}`
);
return response.data;
} catch (error) {
throw error;
@@ -241,3 +251,17 @@ export async function apiInvestmentDeleteNews({ id }: { id: string }) {
throw error;
}
}
export async function apiInvestmentGetInvestorById({
id,
}: {
id: string;
}) {
try {
const response = await apiConfig.get(`/mobile/investment/${id}/investor`);
return response.data;
} catch (error) {
throw error;
}
}