Invesment

Fix:
- tampilan dokumen dan file prospektus
- create & edit dokumen
- list rekap dokumen dan tampilan ( untuk non author )

### No Issue
This commit is contained in:
2025-10-01 14:40:50 +08:00
parent 5f05d1f7f0
commit 250b216a54
7 changed files with 403 additions and 90 deletions

View File

@@ -7,17 +7,78 @@ import {
InformationBox, InformationBox,
Spacing, Spacing,
StackCustom, StackCustom,
TextCustom,
TextInputCustom, TextInputCustom,
ViewWrapper ViewWrapper,
} from "@/components"; } from "@/components";
import { MainColor } from "@/constants/color-palet"; import { MainColor } from "@/constants/color-palet";
import DIRECTORY_ID from "@/constants/directory-id";
import { apiInvestmentUpsertDocument } from "@/service/api-client/api-investment";
import { uploadFileService } from "@/service/upload-service";
import pickFile from "@/utils/pickFile";
import { FontAwesome5 } from "@expo/vector-icons"; import { FontAwesome5 } from "@expo/vector-icons";
import { router } from "expo-router"; import { router, useLocalSearchParams } from "expo-router";
import { useState } from "react";
import Toast from "react-native-toast-message";
export default function InvestmentAddDocument() { export default function InvestmentAddDocument() {
const { id } = useLocalSearchParams();
const [data, setData] = useState({
title: "",
});
const [pdf, setPdf] = useState<any>(null);
const [isLoading, setIsLoading] = useState<boolean>(false);
const handlerSubmit = async () => {
try {
setIsLoading(true);
const responseUploadFile = await uploadFileService({
dirId: DIRECTORY_ID.investasi_dokumen,
imageUri: pdf.uri,
});
if (!responseUploadFile.success) {
throw new Error(responseUploadFile.message);
}
const newData = {
title: data.title,
fileId: responseUploadFile.data.id,
};
const response = await apiInvestmentUpsertDocument({
id: id as string,
data: newData,
});
if (response.success) {
Toast.show({
type: "success",
text1: "Data berhasil disimpan",
});
router.back();
}
} catch (error) {
console.log("[ERROR]", error);
Toast.show({
type: "error",
text1: "Gagal menyimpan data",
});
} finally {
setIsLoading(false);
}
};
const buttonFooter = ( const buttonFooter = (
<BoxButtonOnFooter> <BoxButtonOnFooter>
<ButtonCustom onPress={() => router.back()}>Simpan</ButtonCustom> <ButtonCustom
isLoading={isLoading}
disabled={!pdf || data.title.length <= 0}
onPress={handlerSubmit}
>
Simpan
</ButtonCustom>
</BoxButtonOnFooter> </BoxButtonOnFooter>
); );
@@ -31,22 +92,33 @@ export default function InvestmentAddDocument() {
label="Judul Dokumen" label="Judul Dokumen"
placeholder="Masukan judul dokumen" placeholder="Masukan judul dokumen"
required required
value={data.title}
onChangeText={(value) => setData({ ...data, title: value })}
/> />
<BaseBox> <BaseBox>
<CenterCustom> <CenterCustom>
<FontAwesome5 {pdf ? (
name="file-pdf" <TextCustom truncate>{pdf.name}</TextCustom>
size={30} ) : (
color={MainColor.disabled} <FontAwesome5
/> name="file-pdf"
size={30}
color={MainColor.disabled}
/>
)}
</CenterCustom> </CenterCustom>
</BaseBox> </BaseBox>
<ButtonCenteredOnly <ButtonCenteredOnly
icon="upload" icon="upload"
onPress={() => onPress={() =>
router.push("/(application)/(image)/take-picture/123") pickFile({
allowedType: "pdf",
setPdfUri(file) {
setPdf(file);
},
})
} }
> >
Upload Upload

View File

@@ -1,3 +1,4 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { import {
BaseBox, BaseBox,
BoxButtonOnFooter, BoxButtonOnFooter,
@@ -7,17 +8,99 @@ import {
InformationBox, InformationBox,
Spacing, Spacing,
StackCustom, StackCustom,
TextCustom,
TextInputCustom, TextInputCustom,
ViewWrapper, ViewWrapper,
} from "@/components"; } from "@/components";
import { MainColor } from "@/constants/color-palet"; import DIRECTORY_ID from "@/constants/directory-id";
import { FontAwesome5 } from "@expo/vector-icons"; import {
import { router } from "expo-router"; apiInvestmentGetDocument,
apiInvestmentUpsertDocument,
} from "@/service/api-client/api-investment";
import { deleteFileService, uploadFileService } from "@/service/upload-service";
import pickFile from "@/utils/pickFile";
import { router, useLocalSearchParams } from "expo-router";
import _ from "lodash";
import { useEffect, useState } from "react";
import Toast from "react-native-toast-message";
export default function InvestmentEditDocument() { export default function InvestmentEditDocument() {
const { id } = useLocalSearchParams();
const [data, setData] = useState<any>(null);
const [pdf, setPdf] = useState<any>(null);
const [titleFile, setTitleFile] = useState<string>("");
const [isLoading, setIsLoading] = useState<boolean>(false);
useEffect(() => {
onLoadData();
}, [id]);
const onLoadData = async () => {
try {
const response = await apiInvestmentGetDocument({
id: id as string,
category: "one-document",
});
setData(response.data);
setTitleFile(response.data.title);
} catch (error) {
console.log("[ERROR]", error);
}
};
const handlerUpdate = async () => {
const prevFileId = data.fileId;
try {
setIsLoading(true);
const responseUploadFile = await uploadFileService({
dirId: DIRECTORY_ID.investasi_dokumen,
imageUri: pdf.uri,
});
if (!responseUploadFile.success) {
throw new Error(responseUploadFile.message);
}
const newData = {
title: data.title,
fileId: responseUploadFile.data.id,
};
const response = await apiInvestmentUpsertDocument({
id: id as string,
data: newData,
});
if (response.success) {
const delPrevFile = await deleteFileService({
id: prevFileId,
});
console.log("[DEL PREV FILE]", delPrevFile);
Toast.show({
type: "success",
text1: "Data berhasil diupdate",
});
router.back();
}
} catch (error) {
console.log("[ERROR]", error);
Toast.show({
type: "error",
text1: "Gagal mengupdate data",
});
} finally {
setIsLoading(false);
}
};
const buttonFooter = ( const buttonFooter = (
<BoxButtonOnFooter> <BoxButtonOnFooter>
<ButtonCustom onPress={() => router.back()}>Update</ButtonCustom> <ButtonCustom isLoading={isLoading} disabled={!pdf} onPress={handlerUpdate}>
Update
</ButtonCustom>
</BoxButtonOnFooter> </BoxButtonOnFooter>
); );
@@ -31,22 +114,29 @@ export default function InvestmentEditDocument() {
label="Judul Dokumen" label="Judul Dokumen"
placeholder="Masukan judul dokumen" placeholder="Masukan judul dokumen"
required required
value={data?.title}
onChangeText={(value) => setData({ ...data, title: value })}
/> />
<BaseBox> <BaseBox>
<CenterCustom> <CenterCustom>
<FontAwesome5 {pdf ? (
name="file-pdf" <TextCustom truncate>{pdf.name}</TextCustom>
size={30} ) : (
color={MainColor.disabled} <TextCustom truncate>{_.snakeCase(titleFile || "")}.pdf</TextCustom>
/> )}
</CenterCustom> </CenterCustom>
</BaseBox> </BaseBox>
<ButtonCenteredOnly <ButtonCenteredOnly
icon="upload" icon="upload"
onPress={() => onPress={() =>
router.push("/(application)/(image)/take-picture/123") pickFile({
allowedType: "pdf",
setPdfUri(file) {
setPdf(file);
},
})
} }
> >
Upload Upload

View File

@@ -1,16 +1,58 @@
import { ViewWrapper } from "@/components"; /* eslint-disable react-hooks/exhaustive-deps */
import { LoaderCustom, TextCustom, ViewWrapper } from "@/components";
import Investment_BoxDetailDocument from "@/screens/Invesment/Document/RecapBoxDetail"; import Investment_BoxDetailDocument from "@/screens/Invesment/Document/RecapBoxDetail";
import { apiInvestmentGetDocument } from "@/service/api-client/api-investment";
import { useFocusEffect, useLocalSearchParams } from "expo-router";
import _ from "lodash";
import { useCallback, useState } from "react";
export default function InvestmentListOfDocument() { export default function InvestmentListOfDocument() {
const { id } = useLocalSearchParams();
console.log("ID >> ", id);
const [list, setList] = useState<any[] | null>(null);
const [loadList, setLoadList] = useState(false);
useFocusEffect(
useCallback(() => {
onLoadListDocument();
}, [id])
);
const onLoadListDocument = async () => {
try {
setLoadList(true);
const response = await apiInvestmentGetDocument({
id: id as string,
category: "all-document",
});
setList(response.data);
} catch (error) {
console.log("[ERROR]", error);
setList([]);
} finally {
setLoadList(false);
}
};
return ( return (
<ViewWrapper> <ViewWrapper>
{Array.from({ length: 10 }).map((_, index) => ( {loadList ? (
<Investment_BoxDetailDocument <LoaderCustom />
key={index} ) : _.isEmpty(list) ? (
title={`Judul Dokumen ${index + 1}`} <TextCustom align="center" color="gray">
href={`/(file)/${index + 1}`} Tidak ada data
/> </TextCustom>
))} ) : (
list?.map((item: any, index: number) => (
<Investment_BoxDetailDocument
key={index}
title={item.title}
href={`/(file)/${item.fileId}`}
/>
))
)}
</ViewWrapper> </ViewWrapper>
); );
} }

View File

@@ -1,23 +1,90 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { import {
AlertDefaultSystem, AlertDefaultSystem,
BackButton, BackButton,
DotButton, DotButton,
DrawerCustom, DrawerCustom,
LoaderCustom,
MenuDrawerDynamicGrid, MenuDrawerDynamicGrid,
TextCustom,
ViewWrapper, ViewWrapper,
} from "@/components"; } from "@/components";
import { IconEdit } from "@/components/_Icon"; import { IconEdit } from "@/components/_Icon";
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 Investment_BoxDetailDocument from "@/screens/Invesment/Document/RecapBoxDetail"; import Investment_BoxDetailDocument from "@/screens/Invesment/Document/RecapBoxDetail";
import {
apiInvestmentDeleteDocument,
apiInvestmentGetDocument,
} from "@/service/api-client/api-investment";
import { AntDesign, Ionicons } from "@expo/vector-icons"; import { AntDesign, Ionicons } from "@expo/vector-icons";
import { router, Stack, useLocalSearchParams } from "expo-router"; import {
import { useState } from "react"; router,
Stack,
useFocusEffect,
useLocalSearchParams,
} from "expo-router";
import _ from "lodash";
import { useCallback, useState } from "react";
import Toast from "react-native-toast-message";
export default function InvestmentRecapOfDocument() { export default function InvestmentRecapOfDocument() {
const { id } = useLocalSearchParams(); const { id } = useLocalSearchParams();
const [openDrawer, setOpenDrawer] = useState(false); const [openDrawer, setOpenDrawer] = useState(false);
const [openDrawerBox, setOpenDrawerBox] = useState(false); const [openDrawerBox, setOpenDrawerBox] = useState(false);
const [list, setList] = useState<any[] | null>(null);
const [loadList, setLoadList] = useState(false);
const [selectId, setSelectId] = useState<string | null>(null);
useFocusEffect(
useCallback(() => {
onLoadListDocument();
}, [id])
);
const onLoadListDocument = async () => {
try {
setLoadList(true);
const response = await apiInvestmentGetDocument({
id: id as string,
category: "all-document",
});
setList(response.data);
} catch (error) {
console.log("[ERROR]", error);
setList([]);
} finally {
setLoadList(false);
}
};
const handlerDeleteDocument = async () => {
try {
const response = await apiInvestmentDeleteDocument({
id: selectId as string,
});
if (response.success) {
Toast.show({
type: "success",
text1: "Data berhasil dihapus",
});
setList((prev: any[] | null) => {
if (!prev) return null;
return prev.filter((item: any) => item.id !== selectId);
});
setOpenDrawerBox(false);
setSelectId(null);
}
} catch (error) {
console.log("[ERROR]", error);
Toast.show({
type: "error",
text1: "Gagal menghapus data",
});
}
};
return ( return (
<> <>
@@ -37,25 +104,36 @@ export default function InvestmentRecapOfDocument() {
/> />
<ViewWrapper> <ViewWrapper>
{Array.from({ length: 10 }).map((_, index) => ( {loadList ? (
<Investment_BoxDetailDocument <LoaderCustom />
key={index} ) : _.isEmpty(list) ? (
title={`Judul Dokumen ${index + 1}`} <TextCustom align="center" color="gray">
leftIcon={ Tidak ada data
<Ionicons </TextCustom>
name="ellipsis-horizontal-outline" ) : (
size={ICON_SIZE_SMALL} list?.map((item: any, index: number) => (
color={MainColor.white} <Investment_BoxDetailDocument
style={{ key={index}
zIndex: 10, title={item.title}
alignSelf: "flex-end", leftIcon={
}} <Ionicons
onPress={() => setOpenDrawerBox(true)} name="ellipsis-horizontal-outline"
/> size={ICON_SIZE_SMALL}
} color={MainColor.white}
href={`/(file)/${id}`} style={{
/> zIndex: 10,
))} alignSelf: "flex-end",
}}
onPress={() => {
setSelectId(item.id);
setOpenDrawerBox(true);
}}
/>
}
href={`/(file)/${item.fileId}`}
/>
))
)}
</ViewWrapper> </ViewWrapper>
{/* Drawer On Header */} {/* Drawer On Header */}
@@ -69,7 +147,7 @@ export default function InvestmentRecapOfDocument() {
{ {
icon: ( icon: (
<AntDesign <AntDesign
name="pluscircle" name="plus-circle"
size={ICON_SIZE_SMALL} size={ICON_SIZE_SMALL}
color={MainColor.white} color={MainColor.white}
/> />
@@ -96,7 +174,7 @@ export default function InvestmentRecapOfDocument() {
{ {
icon: <IconEdit />, icon: <IconEdit />,
label: "Edit Dokumen", label: "Edit Dokumen",
path: `/investment/${id}/(document)/edit-document`, path: `/investment/${selectId}/(document)/edit-document`,
}, },
{ {
icon: ( icon: (
@@ -119,12 +197,14 @@ export default function InvestmentRecapOfDocument() {
textLeft: "Batal", textLeft: "Batal",
textRight: "Hapus", textRight: "Hapus",
onPressRight: () => { onPressRight: () => {
setOpenDrawerBox(false); handlerDeleteDocument();
}, },
}); });
} else {
router.push(item.path as any);
} }
router.push(item.path as any);
setOpenDrawer(false); setOpenDrawerBox(false);
}} }}
/> />
</DrawerCustom> </DrawerCustom>

View File

@@ -3,7 +3,6 @@ import {
apiInvestmentDelete, apiInvestmentDelete,
apiInvestmentUpdateStatus, apiInvestmentUpdateStatus,
} from "@/service/api-client/api-investment"; } from "@/service/api-client/api-investment";
import { deleteFileService } from "@/service/upload-service";
import { router } from "expo-router"; import { router } from "expo-router";
import { useState } from "react"; import { useState } from "react";
import Toast from "react-native-toast-message"; import Toast from "react-native-toast-message";
@@ -136,36 +135,14 @@ export default function Investment_ButtonStatusSection({
id: id as string, id: id as string,
}); });
console.log("[RESPONSE]", JSON.stringify(response, null, 2)); console.log("[RESPONSE DELETE]", JSON.stringify(response, null, 2));
if (response.success) { if (response.success) {
const deleteImage = await deleteFileService({
id: response?.data?.imageId as string,
});
if (!deleteImage.success) {
Toast.show({
type: "error",
text1: "Gagal Hapus Data",
});
return;
}
const deleteFile = await deleteFileService({
id: response?.data?.prospektusFileId as string,
});
if (!deleteFile.success) {
Toast.show({
type: "error",
text1: "Gagal Hapus Data",
});
return;
}
Toast.show({ Toast.show({
type: "success", type: "success",
text1: "Berhasil Hapus Data", text1: "Berhasil Hapus Data",
}); });
router.back(); router.back();
} else { } else {
Toast.show({ Toast.show({

View File

@@ -1,5 +1,5 @@
import { BaseBox, Grid, TextCustom } from "@/components"; import { BaseBox, ClickableCustom, Grid, TextCustom } from "@/components";
import { Href } from "expo-router"; import { Href, router } from "expo-router";
export default function Investment_BoxDetailDocument({ export default function Investment_BoxDetailDocument({
title, title,
@@ -8,17 +8,19 @@ export default function Investment_BoxDetailDocument({
}: { }: {
title: string; title: string;
leftIcon?: React.ReactNode; leftIcon?: React.ReactNode;
href?: Href; href?: Href | string;
}) { }) {
return ( return (
<> <>
<BaseBox href={href}> <BaseBox>
<Grid> <Grid>
<Grid.Col span={leftIcon ? 10 : 12}> <Grid.Col span={leftIcon ? 10 : 12}>
<TextCustom truncate> <ClickableCustom onPress={() => router.push(href as any)}>
{title || <TextCustom truncate>
`Judul Dokumen: Lorem, ipsum dolor sit amet consectetur adipisicing elit.`} {title ||
</TextCustom> `Judul Dokumen: Lorem, ipsum dolor sit amet consectetur adipisicing elit.`}
</TextCustom>
</ClickableCustom>
</Grid.Col> </Grid.Col>
{leftIcon && <Grid.Col span={2}>{leftIcon}</Grid.Col>} {leftIcon && <Grid.Col span={2}>{leftIcon}</Grid.Col>}
</Grid> </Grid>

View File

@@ -44,7 +44,6 @@ export async function apiInvestmentUpdateStatus({
id: string; id: string;
status: "publish" | "draft" | "review" | "reject"; status: "publish" | "draft" | "review" | "reject";
}) { }) {
try { try {
const response = await apiConfig.put(`/mobile/investment/${id}/${status}`); const response = await apiConfig.put(`/mobile/investment/${id}/${status}`);
return response.data; return response.data;
@@ -71,9 +70,28 @@ export async function apiInvestmentUpdateData({
data: any; data: any;
category: "data" | "prospectus"; category: "data" | "prospectus";
}) { }) {
try { try {
const response = await apiConfig.put(`/mobile/investment/${id}?category=${category}`, { const response = await apiConfig.put(
`/mobile/investment/${id}?category=${category}`,
{
data: data,
}
);
return response.data;
} catch (error) {
throw error;
}
}
export async function apiInvestmentUpsertDocument({
id,
data,
}: {
id: string;
data: any;
}) {
try {
const response = await apiConfig.post(`/mobile/investment/${id}/document`, {
data: data, data: data,
}); });
return response.data; return response.data;
@@ -81,3 +99,35 @@ export async function apiInvestmentUpdateData({
throw error; throw error;
} }
} }
export async function apiInvestmentGetDocument({
id,
category,
}: {
id: string;
category: "one-document" | "all-document";
}) {
try {
const response = await apiConfig.get(
`/mobile/investment/${id}/document?category=${category}`
);
return response.data;
} catch (error) {
throw error;
}
}
export async function apiInvestmentDeleteDocument({
id,
}: {
id: string;
}) {
try {
const response = await apiConfig.delete(
`/mobile/investment/${id}/document`
);
return response.data;
} catch (error) {
throw error;
}
}