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,
Spacing,
StackCustom,
TextCustom,
TextInputCustom,
ViewWrapper
ViewWrapper,
} from "@/components";
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 { 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() {
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 = (
<BoxButtonOnFooter>
<ButtonCustom onPress={() => router.back()}>Simpan</ButtonCustom>
<ButtonCustom
isLoading={isLoading}
disabled={!pdf || data.title.length <= 0}
onPress={handlerSubmit}
>
Simpan
</ButtonCustom>
</BoxButtonOnFooter>
);
@@ -31,22 +92,33 @@ export default function InvestmentAddDocument() {
label="Judul Dokumen"
placeholder="Masukan judul dokumen"
required
value={data.title}
onChangeText={(value) => setData({ ...data, title: value })}
/>
<BaseBox>
<CenterCustom>
<FontAwesome5
name="file-pdf"
size={30}
color={MainColor.disabled}
/>
{pdf ? (
<TextCustom truncate>{pdf.name}</TextCustom>
) : (
<FontAwesome5
name="file-pdf"
size={30}
color={MainColor.disabled}
/>
)}
</CenterCustom>
</BaseBox>
<ButtonCenteredOnly
icon="upload"
onPress={() =>
router.push("/(application)/(image)/take-picture/123")
pickFile({
allowedType: "pdf",
setPdfUri(file) {
setPdf(file);
},
})
}
>
Upload

View File

@@ -1,3 +1,4 @@
/* eslint-disable react-hooks/exhaustive-deps */
import {
BaseBox,
BoxButtonOnFooter,
@@ -7,17 +8,99 @@ import {
InformationBox,
Spacing,
StackCustom,
TextCustom,
TextInputCustom,
ViewWrapper,
} from "@/components";
import { MainColor } from "@/constants/color-palet";
import { FontAwesome5 } from "@expo/vector-icons";
import { router } from "expo-router";
import DIRECTORY_ID from "@/constants/directory-id";
import {
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() {
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 = (
<BoxButtonOnFooter>
<ButtonCustom onPress={() => router.back()}>Update</ButtonCustom>
<ButtonCustom isLoading={isLoading} disabled={!pdf} onPress={handlerUpdate}>
Update
</ButtonCustom>
</BoxButtonOnFooter>
);
@@ -31,22 +114,29 @@ export default function InvestmentEditDocument() {
label="Judul Dokumen"
placeholder="Masukan judul dokumen"
required
value={data?.title}
onChangeText={(value) => setData({ ...data, title: value })}
/>
<BaseBox>
<CenterCustom>
<FontAwesome5
name="file-pdf"
size={30}
color={MainColor.disabled}
/>
{pdf ? (
<TextCustom truncate>{pdf.name}</TextCustom>
) : (
<TextCustom truncate>{_.snakeCase(titleFile || "")}.pdf</TextCustom>
)}
</CenterCustom>
</BaseBox>
<ButtonCenteredOnly
icon="upload"
onPress={() =>
router.push("/(application)/(image)/take-picture/123")
pickFile({
allowedType: "pdf",
setPdfUri(file) {
setPdf(file);
},
})
}
>
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 { 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() {
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 (
<ViewWrapper>
{Array.from({ length: 10 }).map((_, index) => (
<Investment_BoxDetailDocument
key={index}
title={`Judul Dokumen ${index + 1}`}
href={`/(file)/${index + 1}`}
/>
))}
{loadList ? (
<LoaderCustom />
) : _.isEmpty(list) ? (
<TextCustom align="center" color="gray">
Tidak ada data
</TextCustom>
) : (
list?.map((item: any, index: number) => (
<Investment_BoxDetailDocument
key={index}
title={item.title}
href={`/(file)/${item.fileId}`}
/>
))
)}
</ViewWrapper>
);
}

View File

@@ -1,23 +1,90 @@
/* eslint-disable react-hooks/exhaustive-deps */
import {
AlertDefaultSystem,
BackButton,
DotButton,
DrawerCustom,
LoaderCustom,
MenuDrawerDynamicGrid,
TextCustom,
ViewWrapper,
} from "@/components";
import { IconEdit } from "@/components/_Icon";
import { MainColor } from "@/constants/color-palet";
import { ICON_SIZE_SMALL } from "@/constants/constans-value";
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 { router, Stack, useLocalSearchParams } from "expo-router";
import { useState } from "react";
import {
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() {
const { id } = useLocalSearchParams();
const [openDrawer, setOpenDrawer] = 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 (
<>
@@ -37,25 +104,36 @@ export default function InvestmentRecapOfDocument() {
/>
<ViewWrapper>
{Array.from({ length: 10 }).map((_, index) => (
<Investment_BoxDetailDocument
key={index}
title={`Judul Dokumen ${index + 1}`}
leftIcon={
<Ionicons
name="ellipsis-horizontal-outline"
size={ICON_SIZE_SMALL}
color={MainColor.white}
style={{
zIndex: 10,
alignSelf: "flex-end",
}}
onPress={() => setOpenDrawerBox(true)}
/>
}
href={`/(file)/${id}`}
/>
))}
{loadList ? (
<LoaderCustom />
) : _.isEmpty(list) ? (
<TextCustom align="center" color="gray">
Tidak ada data
</TextCustom>
) : (
list?.map((item: any, index: number) => (
<Investment_BoxDetailDocument
key={index}
title={item.title}
leftIcon={
<Ionicons
name="ellipsis-horizontal-outline"
size={ICON_SIZE_SMALL}
color={MainColor.white}
style={{
zIndex: 10,
alignSelf: "flex-end",
}}
onPress={() => {
setSelectId(item.id);
setOpenDrawerBox(true);
}}
/>
}
href={`/(file)/${item.fileId}`}
/>
))
)}
</ViewWrapper>
{/* Drawer On Header */}
@@ -69,7 +147,7 @@ export default function InvestmentRecapOfDocument() {
{
icon: (
<AntDesign
name="pluscircle"
name="plus-circle"
size={ICON_SIZE_SMALL}
color={MainColor.white}
/>
@@ -96,7 +174,7 @@ export default function InvestmentRecapOfDocument() {
{
icon: <IconEdit />,
label: "Edit Dokumen",
path: `/investment/${id}/(document)/edit-document`,
path: `/investment/${selectId}/(document)/edit-document`,
},
{
icon: (
@@ -119,12 +197,14 @@ export default function InvestmentRecapOfDocument() {
textLeft: "Batal",
textRight: "Hapus",
onPressRight: () => {
setOpenDrawerBox(false);
handlerDeleteDocument();
},
});
} else {
router.push(item.path as any);
}
router.push(item.path as any);
setOpenDrawer(false);
setOpenDrawerBox(false);
}}
/>
</DrawerCustom>

View File

@@ -3,7 +3,6 @@ import {
apiInvestmentDelete,
apiInvestmentUpdateStatus,
} from "@/service/api-client/api-investment";
import { deleteFileService } from "@/service/upload-service";
import { router } from "expo-router";
import { useState } from "react";
import Toast from "react-native-toast-message";
@@ -136,36 +135,14 @@ export default function Investment_ButtonStatusSection({
id: id as string,
});
console.log("[RESPONSE]", JSON.stringify(response, null, 2));
console.log("[RESPONSE DELETE]", JSON.stringify(response, null, 2));
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({
type: "success",
text1: "Berhasil Hapus Data",
});
router.back();
} else {
Toast.show({

View File

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

View File

@@ -44,7 +44,6 @@ export async function apiInvestmentUpdateStatus({
id: string;
status: "publish" | "draft" | "review" | "reject";
}) {
try {
const response = await apiConfig.put(`/mobile/investment/${id}/${status}`);
return response.data;
@@ -71,9 +70,28 @@ export async function apiInvestmentUpdateData({
data: any;
category: "data" | "prospectus";
}) {
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,
});
return response.data;
@@ -81,3 +99,35 @@ export async function apiInvestmentUpdateData({
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;
}
}