Compare commits

..

10 Commits

Author SHA1 Message Date
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
b3209dc7ee Integrasi API: Donation & Admin Donation
Fix:
- app/(application)/(user)/donation/[id]/fund-disbursement.tsx
- app/(application)/(user)/donation/[id]/list-of-donatur.tsx
- app/(application)/admin/donation/[id]/[status]/index.tsx
- app/(application)/admin/donation/[id]/detail-disbursement-of-funds.tsx
- app/(application)/admin/donation/[id]/disbursement-of-funds.tsx
- app/(application)/admin/donation/[id]/list-disbursement-of-funds.tsx
- service/api-admin/api-admin-donation.ts
- service/api-client/api-donation.ts
- utils/pickFile.ts: Sudah bisa memilih ukuran crop tapi hanya di android

### No issue
2025-10-29 17:35:18 +08:00
1e1b18f860 Integrasi API: Donation & Admin Donation
Fix:
- app/(application)/(user)/donation/[id]/(transaction-flow)/[invoiceId]/invoice.tsx
- app/(application)/(user)/donation/[id]/index.tsx
- app/(application)/admin/donation/[id]/[status]/index.tsx
- app/(application)/admin/donation/[id]/[status]/transaction-detail.tsx
- app/(application)/admin/donation/[id]/list-of-donatur.tsx
- components/Select/SelectCustom.tsx
- context/AuthContext.tsx
- screens/Donation/BoxPublish.tsx
- screens/Donation/ProgressSection.tsx
- service/api-admin/api-admin-donation.ts

### NO Issue
2025-10-28 17:45:13 +08:00
5d4328a139 Integrasi API: Donation Admin
Add:
-  screens/Admin/Donation/funDonationUpdateStatus.ts
-  utils/countDownAndCondition.ts

Fix:
- app/(application)/(user)/donation/[id]/index.tsx
- app/(application)/admin/donation/[id]/[status]/index.tsx
- app/(application)/admin/donation/[id]/list-of-donatur.tsx
- app/(application)/admin/donation/[id]/reject-input.tsx
- app/(application)/admin/donation/index.tsx
- app/(application)/admin/event/[id]/[status]/index.tsx
- app/(application)/admin/voting/[id]/[status]/index.tsx
- screens/Admin/Donation/BoxOfDonationStory.tsx
- screens/Donation/BoxPublish.tsx
- screens/Donation/ComponentBoxDetailData.tsx
- service/api-admin/api-admin-donation.ts
- service/api-client/api-master.ts
- utils/colorBadge.ts
git add . && git commit -m
2025-10-28 10:19:47 +08:00
125bf16605 Update new github 2025-10-27 10:51:57 +08:00
73a803f2e8 Update new github 2025-10-27 10:49:31 +08:00
1bcd1a044f Integrasi API: Event Type
Add:
- utils/colorActivationForBadge.ts

Fix:
- app/(application)/admin/event/type-create.tsx
- app/(application)/admin/event/type-of-event.tsx
- app/(application)/admin/event/type-update.tsx
- service/api-admin/api-admin-event.ts
- service/api-admin/api-master-admin.ts
- service/api-client/api-event.ts

### No Issue
2025-10-24 16:22:45 +08:00
1e0b72de22 Integrasi API: Event Qr Code
Fix:
- app/(application)/(user)/event/[id]/confirmation.tsx
- app/(application)/(user)/event/[id]/list-of-participants.tsx
- app/(application)/admin/event/[id]/[status]/index.tsx
- app/(application)/admin/event/[id]/list-of-participants.tsx
- components/DateInput/DataTimeAndroid.tsx
- components/DateInput/DateTimeIOS.tsx
- service/api-admin/api-admin-event.ts

### No Issue
2025-10-24 11:57:05 +08:00
52 changed files with 2880 additions and 888 deletions

View File

@@ -122,7 +122,7 @@ export default function DonationInvoice() {
}} }}
> >
<TextCustom size="xlarge" bold color="yellow"> <TextCustom size="xlarge" bold color="yellow">
{data?.DonasiMaster_Bank?.norek} {data?.MasterBank?.norek}
</TextCustom> </TextCustom>
</Grid.Col> </Grid.Col>
<Grid.Col <Grid.Col
@@ -131,7 +131,7 @@ export default function DonationInvoice() {
alignItems: "flex-end", alignItems: "flex-end",
}} }}
> >
<CopyButton textToCopy={data?.DonasiMaster_Bank?.norek} /> <CopyButton textToCopy={data?.MasterBank?.norek} />
</Grid.Col> </Grid.Col>
</Grid> </Grid>
</BaseBox> </BaseBox>

View File

@@ -1,17 +1,71 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { import {
BaseBox, BaseBox,
ButtonCenteredOnly, ButtonCenteredOnly,
Grid, Grid,
InformationBox, InformationBox,
LoaderCustom,
StackCustom, StackCustom,
TextCustom, TextCustom,
ViewWrapper, ViewWrapper,
} from "@/components"; } from "@/components";
import {
apiDonationDisbursementOfFundsListById,
apiDonationGetOne,
} from "@/service/api-client/api-donation";
import { formatCurrencyDisplay } from "@/utils/formatCurrencyDisplay";
import dayjs from "dayjs"; import dayjs from "dayjs";
import { router, useLocalSearchParams } from "expo-router"; import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
import _ from "lodash";
import React, { useState } from "react";
export default function DonationFundDisbursement() { export default function DonationFundDisbursement() {
const { id } = useLocalSearchParams(); const { id } = useLocalSearchParams();
const [data, setData] = useState({
totalPencairan: 0,
akumulasiPencairan: 0,
});
const [listData, setListData] = React.useState<any[] | null>(null);
const [loadData, setLoadData] = React.useState(false);
useFocusEffect(
React.useCallback(() => {
onLoadData();
}, [id])
);
const onLoadData = async () => {
try {
setLoadData(true);
const responseData = await apiDonationGetOne({
id: id as string,
category: "permanent",
});
if (responseData.success) {
setData({
totalPencairan: responseData.data.totalPencairan,
akumulasiPencairan: responseData.data.akumulasiPencairan,
});
}
const responseList = await apiDonationDisbursementOfFundsListById({
id: id as string,
});
if (responseList.success) {
setListData(responseList.data);
}
} catch (error) {
console.log("[ERROR]", error);
} finally {
setLoadData(false);
}
};
return ( return (
<> <>
<ViewWrapper> <ViewWrapper>
@@ -20,47 +74,50 @@ export default function DonationFundDisbursement() {
<Grid> <Grid>
<Grid.Col span={6}> <Grid.Col span={6}>
<TextCustom bold color="yellow"> <TextCustom bold color="yellow">
Rp. 0 Rp. {formatCurrencyDisplay(data?.totalPencairan)}
</TextCustom> </TextCustom>
<TextCustom size="small">Total Pencairan Dana</TextCustom> <TextCustom size="small">Total Pencairan Dana</TextCustom>
</Grid.Col> </Grid.Col>
<Grid.Col span={6}> <Grid.Col span={6}>
<TextCustom bold color="yellow"> <TextCustom bold color="yellow">
0 kali {data?.akumulasiPencairan} kali
</TextCustom> </TextCustom>
<TextCustom size="small">Akumulasi Pencairan</TextCustom> <TextCustom size="small">Akumulasi Pencairan</TextCustom>
</Grid.Col> </Grid.Col>
</Grid> </Grid>
</BaseBox> </BaseBox>
{Array.from({ length: 10 }).map((_, index) => ( {loadData ? (
<BaseBox key={index}> <LoaderCustom />
<StackCustom> ) : _.isEmpty(listData) ? (
<Grid> <TextCustom align="center" color="gray">
<Grid.Col span={8}> Belum ada data
<TextCustom bold>Pencairan ke - {index + 1}</TextCustom> </TextCustom>
</Grid.Col> ) : (
<Grid.Col span={4} style={{ alignItems: "flex-end" }}> listData?.map((item, index) => (
<TextCustom>{dayjs().format("DD MMM YYYY")}</TextCustom> <BaseBox key={index}>
</Grid.Col> <StackCustom>
</Grid> <Grid>
<TextCustom> <Grid.Col span={8}>
Lorem ipsum dolor sit amet consectetur adipisicing elit. <TextCustom bold>{item?.title}</TextCustom>
Nesciunt dolor ad sit? Eaque rem nihil natus, id, esse possimus </Grid.Col>
perferendis provident velit illo consectetur distinctio ab <Grid.Col span={4} style={{ alignItems: "flex-end" }}>
accusantium quis earum omnis! <TextCustom>{dayjs(item?.createdAt).format("DD MMM YYYY")}</TextCustom>
</TextCustom> </Grid.Col>
<ButtonCenteredOnly </Grid>
onPress={() => { <TextCustom>{item?.deskripsi}</TextCustom>
router.navigate(`/(application)/(file)/${id}`); <ButtonCenteredOnly
}} onPress={() => {
icon="file-text" router.navigate(`/(application)/(image)/preview-image/${item?.imageId}`);
> }}
Bukti Transaksi icon="file-text"
</ButtonCenteredOnly> >
</StackCustom> Bukti Transaksi
</BaseBox> </ButtonCenteredOnly>
))} </StackCustom>
</BaseBox>
))
)}
</ViewWrapper> </ViewWrapper>
</> </>
); );

View File

@@ -16,20 +16,19 @@ import Donation_ComponentInfoFundrising from "@/screens/Donation/ComponentInfoFu
import Donation_ComponentStoryFunrising from "@/screens/Donation/ComponentStoryFunrising"; import Donation_ComponentStoryFunrising from "@/screens/Donation/ComponentStoryFunrising";
import Donation_ProgressSection from "@/screens/Donation/ProgressSection"; import Donation_ProgressSection from "@/screens/Donation/ProgressSection";
import { apiDonationGetOne } from "@/service/api-client/api-donation"; import { apiDonationGetOne } from "@/service/api-client/api-donation";
import { countDownAndCondition } from "@/utils/countDownAndCondition";
import { import {
router, router,
Stack, Stack,
useFocusEffect, useFocusEffect,
useLocalSearchParams, useLocalSearchParams,
} from "expo-router"; } from "expo-router";
import { useCallback, useState } from "react"; import { useCallback, useEffect, useState } from "react";
export default function DonasiDetailBeranda() { export default function DonasiDetailBeranda() {
const { user } = useAuth(); const { user } = useAuth();
const { id } = useLocalSearchParams(); const { id } = useLocalSearchParams();
console.log("ID ", id);
const [openDrawer, setOpenDrawer] = useState(false); const [openDrawer, setOpenDrawer] = useState(false);
const [data, setData] = useState<any>(); const [data, setData] = useState<any>();
useFocusEffect( useFocusEffect(
@@ -45,21 +44,41 @@ export default function DonasiDetailBeranda() {
category: "permanent", category: "permanent",
}); });
console.log("[RES GET ONE]", JSON.stringify(response.data, null, 2));
setData(response.data); setData(response.data);
} catch (error) { } catch (error) {
console.log("[ERROR]", error); console.log("[ERROR]", error);
} }
}; };
const [value, setValue] = useState({
sisa: 0,
reminder: false,
});
useEffect(() => {
updateCountDown();
}, [data]);
const updateCountDown = () => {
const countDown = countDownAndCondition({
duration: data?.DonasiMaster_Durasi?.name,
publishTime: data?.publishTime,
});
setValue({
sisa: countDown.durationDay,
reminder: countDown.reminder,
});
};
const buttonSection = ( const buttonSection = (
<> <>
<BoxButtonOnFooter> <BoxButtonOnFooter>
<ButtonCustom <ButtonCustom
disabled={value?.reminder}
onPress={() => router.navigate(`/donation/${id}/(transaction-flow)`)} onPress={() => router.navigate(`/donation/${id}/(transaction-flow)`)}
> >
Donasi {value?.reminder ? "Waktu berakhir" : "Donasi"}
</ButtonCustom> </ButtonCustom>
</BoxButtonOnFooter> </BoxButtonOnFooter>
</> </>
@@ -80,8 +99,10 @@ export default function DonasiDetailBeranda() {
<ViewWrapper footerComponent={buttonSection}> <ViewWrapper footerComponent={buttonSection}>
<StackCustom> <StackCustom>
<Donation_ComponentBoxDetailData <Donation_ComponentBoxDetailData
sisaHari={value.sisa}
reminder={value.reminder}
data={data} data={data}
bottomSection={<Donation_ProgressSection id={id as string} />} bottomSection={<Donation_ProgressSection id={id as string} progres={Number(data?.progres) || 0} />}
/> />
<Donation_ComponentInfoFundrising dataAuthor={data?.Author} /> <Donation_ComponentInfoFundrising dataAuthor={data?.Author} />
<Donation_ComponentStoryFunrising <Donation_ComponentStoryFunrising

View File

@@ -1,46 +1,93 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { import {
BaseBox, BaseBox,
Grid, Grid,
LoaderCustom,
Spacing,
StackCustom, StackCustom,
TextCustom, TextCustom,
ViewWrapper, ViewWrapper,
} from "@/components"; } from "@/components";
import { MainColor } from "@/constants/color-palet"; import { MainColor } from "@/constants/color-palet";
import { apiAdminDonationListOfDonaturById } from "@/service/api-admin/api-admin-donation";
import { formatCurrencyDisplay } from "@/utils/formatCurrencyDisplay";
import { FontAwesome6 } from "@expo/vector-icons"; import { FontAwesome6 } from "@expo/vector-icons";
import dayjs from "dayjs"; import dayjs from "dayjs";
import { useFocusEffect, useLocalSearchParams } from "expo-router";
import _ from "lodash";
import { useCallback, useState } from "react";
export default function Donation_ListOfDonatur() { export default function Donation_ListOfDonatur() {
const { id } = useLocalSearchParams();
const [listData, setListData] = useState<any[] | null>(null);
const [loadData, setLoadData] = useState(false);
useFocusEffect(
useCallback(() => {
onLoadData();
}, [id])
);
const onLoadData = async () => {
try {
setLoadData(true);
const response = await apiAdminDonationListOfDonaturById({
id: id as string,
});
if (response.success) {
setListData(response.data);
}
} catch (error) {
console.log("[ERROR]", error);
} finally {
setLoadData(false);
}
};
return ( return (
<> <>
<ViewWrapper> <ViewWrapper>
{Array.from({ length: 10 }).map((_, index) => ( {loadData ? (
<BaseBox key={index}> <LoaderCustom />
<Grid> ) : _.isEmpty(listData) ? (
<Grid.Col <TextCustom bold align="center">
span={3} Belum ada donatur
style={{ alignItems: "center", justifyContent: "center" }} </TextCustom>
> ) : (
<FontAwesome6 listData?.map((item: any, index: number) => (
name="face-smile-wink" <BaseBox key={index}>
size={50} <Grid>
style={{ color: MainColor.yellow }} <Grid.Col
/> span={3}
</Grid.Col> style={{ alignItems: "center", justifyContent: "center" }}
<Grid.Col span={9}> >
<StackCustom gap={"xs"}> <FontAwesome6
name="face-smile-wink"
size={50}
style={{ color: MainColor.yellow }}
/>
</Grid.Col>
<Grid.Col span={9}>
<TextCustom bold size="large"> <TextCustom bold size="large">
Username {item?.Author?.username || "-"}
</TextCustom> </TextCustom>
<TextCustom>Berdonas sebesar </TextCustom> <Spacing/>
<TextCustom bold size="large" color="yellow"> <StackCustom gap={"xs"}>
Rp. 100.000 <TextCustom size={"small"}>Berdonas sebesar </TextCustom>
</TextCustom> <TextCustom bold size="large" color="yellow">
<TextCustom>{dayjs().format("DD MMM YYYY")}</TextCustom> Rp. {formatCurrencyDisplay(item?.nominal)}
</StackCustom> </TextCustom>
</Grid.Col> <TextCustom>
</Grid> {dayjs(item?.createdAt).format("DD MMM YYYY, HH:mm")}
</BaseBox> </TextCustom>
))} </StackCustom>
</Grid.Col>
</Grid>
</BaseBox>
))
)}
</ViewWrapper> </ViewWrapper>
</> </>
); );

View File

@@ -74,8 +74,6 @@ export default function UserEventConfirmation() {
userId: user?.id as string, userId: user?.id as string,
}); });
console.log("[RES CONFIRMATION]", JSON.stringify(response, null, 2));
if (response.success) { if (response.success) {
setData(response.data?.dataEvent); setData(response.data?.dataEvent);
setPeserta(response.data?.peserta); setPeserta(response.data?.peserta);
@@ -142,11 +140,11 @@ export default function UserEventConfirmation() {
return ( return (
<TamplateBox data={data}> <TamplateBox data={data}>
<TamplateText <TamplateText
text={`Event telah selesai, anda terdaftar sebagai peserta dan${ text={`Event telah selesai, anda terdaftar sebagai peserta dan ${
konfirmasi konfirmasi
? "Anda telah mengonfirmasi kehadiran." ? "Anda telah mengonfirmasi kehadiran."
: "Anda tidak mengonfirmasi kehadiran." : "Anda tidak mengonfirmasi kehadiran."
}. Terima kasih atas perhatian dan minat Anda. Kami berharap dapat bertemu di acara kami berikutnya.`} } Terima kasih atas perhatian dan minat Anda. Kami berharap dapat bertemu di acara kami berikutnya.`}
/> />
<BackToOtherPath <BackToOtherPath
path="event" path="event"
@@ -173,14 +171,16 @@ export default function UserEventConfirmation() {
if (isWithinConfirmationWindow && peserta === true) { if (isWithinConfirmationWindow && peserta === true) {
if (konfirmasi === false) { if (konfirmasi === false) {
return ( return (
<TamplateBox data={data}> <UserParticipan_And_DuringEvent
<TamplateText text="Konfirmasi Kehadiran" /> id={data.id}
</TamplateBox> userId={user?.id as string}
data={data}
/>
); );
} }
return ( return (
<TamplateBox data={data}> <TamplateBox data={data}>
<TamplateText text="Anda telah mengonfirmasi kehadiran." /> <TamplateText text="Terimakasih telah mengonfirmasi kehadiran. Silahkan lihat peserta lain pada halaman event atau kembali ke halaman home. Selamat menikmati acara dan selamat berpartisipasi." />
<BackToOtherPath <BackToOtherPath
path="event" path="event"
id={data.id} id={data.id}
@@ -192,7 +192,7 @@ export default function UserEventConfirmation() {
return ( return (
<TamplateBox data={data}> <TamplateBox data={data}>
<TamplateText text="Anda terdaftar sebagai peserta. Konfirmasi kehadiran dibuka 1 jam sebelum acara dimulai." /> <TamplateText text="Anda telah terdaftar sebagai peserta pada Event ini. Konfirmasi kehadiran dibuka 1 jam sebelum acara dimulai." />
<BackToOtherPath <BackToOtherPath
path="event" path="event"
id={data.id} id={data.id}
@@ -326,7 +326,7 @@ const TamplateBox = ({
); );
}; };
const TamplateText = ({ text }: { text: string }) => { const TamplateText = ({ text }: { text: React.ReactNode }) => {
return ( return (
<> <>
<TextCustom align="center">{text}</TextCustom> <TextCustom align="center">{text}</TextCustom>
@@ -442,7 +442,7 @@ const NotStarted_And_UserNotParticipan = ({
}; };
// 🟡 ZONA ACARA BERLANGSUNG // 🟡 ZONA ACARA BERLANGSUNG
// Acara sedang berlangsung & belum terdaftar // Acara sedang berlangsung & belum terdaftar & user harus join dan konfirmasi
const UserNotParticipan_And_DuringEvent = ({ const UserNotParticipan_And_DuringEvent = ({
id, id,
userId, userId,
@@ -464,8 +464,6 @@ const UserNotParticipan_And_DuringEvent = ({
category: "join_and_confirm", category: "join_and_confirm",
}); });
// console.log("[RES JOIN & CONFIRMATION EVENT]", response);
if (!response.success) { if (!response.success) {
Toast.show({ Toast.show({
type: "error", type: "error",
@@ -498,3 +496,59 @@ const UserNotParticipan_And_DuringEvent = ({
</> </>
); );
}; };
// 🟡 ZONA ACARA BERLANGSUN
// User sudah terdaftar & Event sedang berlangsung & user harus konfirmasi
const UserParticipan_And_DuringEvent = ({
id,
userId,
data,
}: {
id: string;
userId: string;
data: DataEvent;
}) => {
const [isLoading, setIsLoading] = useState<boolean>(false);
const handlerSubmit = async () => {
try {
setIsLoading(true);
const response = await apiEventConfirmationAction({
id: id as string,
userId: userId as string,
category: "confirmation",
});
if (!response.success) {
Toast.show({
type: "error",
text1: "Anda gagal konfirmasi",
});
return;
}
Toast.show({
type: "success",
text1: "Anda berhasil konfirmasi",
});
router.navigate(`/(application)/(user)/event/${id}/publish`);
} catch (error) {
console.log("[ERROR JOIN & CONFIRMATION EVENT]", error);
} finally {
setIsLoading(false);
}
};
return (
<>
<TamplateBox data={data}>
<TamplateText text="Anda sudah terdaftar sebagai peserta & Event sedang berlangsung. Silahkan konfirmasi kehadiran" />
<ButtonCustom onPress={() => handlerSubmit()} isLoading={isLoading}>
Konfirmasi
</ButtonCustom>
</TamplateBox>
</>
);
};

View File

@@ -11,29 +11,30 @@ import {
apiEventGetOne, apiEventGetOne,
apiEventListOfParticipants, apiEventListOfParticipants,
} from "@/service/api-client/api-event"; } from "@/service/api-client/api-event";
import { useLocalSearchParams } from "expo-router"; import dayjs, { Dayjs } from "dayjs";
import { useEffect, useState } from "react"; import { useFocusEffect, useLocalSearchParams } from "expo-router";
import _ from "lodash";
import { useCallback, useState } from "react";
import { View } from "react-native"; import { View } from "react-native";
export default function EventListOfParticipants() { export default function EventListOfParticipants() {
const { id } = useLocalSearchParams(); const { id } = useLocalSearchParams();
const [startDate, setStartDate] = useState(); const [startDate, setStartDate] = useState<Dayjs | undefined>();
const [listData, setListData] = useState([]); const [listData, setListData] = useState<any[] | null>(null);
const [isLoadData, setIsLoadData] = useState(false); const [loadtData, setLoadData] = useState(false);
useEffect(() => { useFocusEffect(
handlerLoadData(); useCallback(() => {
}, [id]); handlerLoadData();
}, [id])
);
const handlerLoadData = () => { const handlerLoadData = () => {
try { try {
setIsLoadData(true);
onLoadData(); onLoadData();
onLoadList(); onLoadList();
} catch (error) { } catch (error) {
console.log("[ERROR]", error); console.log("[ERROR]", error);
} finally {
setIsLoadData(false);
} }
}; };
@@ -41,7 +42,8 @@ export default function EventListOfParticipants() {
try { try {
const response = await apiEventGetOne({ id: id as string }); const response = await apiEventGetOne({ id: id as string });
if (response.success) { if (response.success) {
setStartDate(response.data.tanggal); const date = dayjs(response.data.tanggal);
setStartDate(date);
} }
} catch (error) { } catch (error) {
console.log("[ERROR]", error); console.log("[ERROR]", error);
@@ -50,30 +52,36 @@ export default function EventListOfParticipants() {
const onLoadList = async () => { const onLoadList = async () => {
try { try {
setLoadData(true);
const response = await apiEventListOfParticipants({ id: id as string }); const response = await apiEventListOfParticipants({ id: id as string });
if (response.success) { if (response.success) {
setListData(response.data); setListData(response.data);
} }
} catch (error) { } catch (error) {
console.log("[ERROR]", error); console.log("[ERROR]", error);
} finally {
setLoadData(false);
} }
}; };
return ( return (
<ViewWrapper> <ViewWrapper>
{isLoadData ? ( {loadtData && !listData ? (
<LoaderCustom /> <LoaderCustom />
) : listData.length === 0 ? ( ) : _.isEmpty(listData) ? (
<TextCustom align="center">Belum ada peserta</TextCustom> <TextCustom align="center" color="gray">
Belum ada peserta
</TextCustom>
) : ( ) : (
listData.map((item: any, index: number) => ( listData?.map((item: any, index: number) => (
<BaseBox key={index}> <BaseBox key={index}>
<AvatarUsernameAndOtherComponent <AvatarUsernameAndOtherComponent
avatar={item?.User?.Profile?.imageId} avatar={item?.User?.Profile?.imageId}
name={item?.User?.username} name={item?.User?.username}
avatarHref={`/profile/${item?.User?.Profile?.id}`} avatarHref={`/profile/${item?.User?.Profile?.id}`}
rightComponent={ rightComponent={
new Date().getTime() > new Date(startDate as any).getTime() ? ( startDate && startDate.subtract(1, "hour").diff(dayjs()) < 0 ? (
<View <View
style={{ style={{
justifyContent: "flex-end", justifyContent: "flex-end",

View File

@@ -1,23 +1,14 @@
import { import {
BaseBox,
FloatingButton, FloatingButton,
Grid,
LoaderCustom, LoaderCustom,
ProgressCustom, ViewWrapper
StackCustom,
TextCustom,
ViewWrapper,
} from "@/components"; } from "@/components";
import API_STRORAGE from "@/constants/base-url-api-strorage"; import NoDataText from "@/components/_ShareComponent/NoDataText";
import DUMMY_IMAGE from "@/constants/dummy-image-value"; import Investment_BoxBerandaSection from "@/screens/Invesment/BoxBerandaSection";
import { apiInvestmentGetAll } from "@/service/api-client/api-investment"; 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 { router, useFocusEffect } from "expo-router";
import _ from "lodash"; import _ from "lodash";
import { useCallback, useState } from "react"; import { useCallback, useState } from "react";
import { View } from "react-native";
export default function InvestmentBursa() { export default function InvestmentBursa() {
const [list, setList] = useState<any[] | null>(null); const [list, setList] = useState<any[] | null>(null);
@@ -33,7 +24,7 @@ export default function InvestmentBursa() {
try { try {
setLoadingList(true); setLoadingList(true);
const response = await apiInvestmentGetAll(); const response = await apiInvestmentGetAll();
console.log("[DATA LIST]", JSON.stringify(response.data, null, 2)); // console.log("[DATA LIST]", JSON.stringify(response.data, null, 2));
setList(response.data); setList(response.data);
} catch (error) { } catch (error) {
console.log("[ERROR]", error); console.log("[ERROR]", error);
@@ -52,95 +43,12 @@ export default function InvestmentBursa() {
{loadingList ? ( {loadingList ? (
<LoaderCustom /> <LoaderCustom />
) : _.isEmpty(list) ? ( ) : _.isEmpty(list) ? (
<TextCustom>Belum ada data</TextCustom> <NoDataText />
) : ( ) : (
list?.map((item: any, index: number) => ( list?.map((item: any, index: number) => (
<BaseBox <Investment_BoxBerandaSection id={item.id} data={item} key={index} />
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>
)) ))
)} )}
</ViewWrapper> </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,3 +1,4 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { import {
ActionIcon, ActionIcon,
AlertDefaultSystem, AlertDefaultSystem,
@@ -18,97 +19,153 @@ import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButt
import AdminButtonReject from "@/components/_ShareComponent/Admin/ButtonReject"; import AdminButtonReject from "@/components/_ShareComponent/Admin/ButtonReject";
import AdminButtonReview from "@/components/_ShareComponent/Admin/ButtonReview"; import AdminButtonReview from "@/components/_ShareComponent/Admin/ButtonReview";
import { GridDetail_4_8 } from "@/components/_ShareComponent/GridDetail_4_8"; import { GridDetail_4_8 } from "@/components/_ShareComponent/GridDetail_4_8";
import { MainColor } from "@/constants/color-palet"; import ReportBox from "@/components/Box/ReportBox";
import { ICON_SIZE_BUTTON, TEXT_SIZE_LARGE } from "@/constants/constans-value"; import { ICON_SIZE_BUTTON, TEXT_SIZE_LARGE } from "@/constants/constans-value";
import AdminDonation_BoxOfDonationStory from "@/screens/Admin/Donation/BoxOfDonationStory"; import AdminDonation_BoxOfDonationStory from "@/screens/Admin/Donation/BoxOfDonationStory";
import { funUpdateStatusDonation } from "@/screens/Admin/Donation/funDonationUpdateStatus";
import { apiAdminDonationDetailById } from "@/service/api-admin/api-admin-donation";
import { colorBadgeStatus } from "@/utils/colorBadge";
import { formatCurrencyDisplay } from "@/utils/formatCurrencyDisplay";
import { Ionicons } from "@expo/vector-icons"; import { Ionicons } from "@expo/vector-icons";
import { router, useLocalSearchParams } from "expo-router"; import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
import _ from "lodash"; import _ from "lodash";
import React from "react"; import React from "react";
import { View } from "react-native"; import { View } from "react-native";
import Toast from "react-native-toast-message";
export default function AdminDonationDetail() { export default function AdminDonationDetail() {
const { id, status } = useLocalSearchParams(); const { id, status } = useLocalSearchParams();
const [openDrawer, setOpenDrawer] = React.useState(false); const [openDrawer, setOpenDrawer] = React.useState(false);
const colorBadge = () => { const [data, setData] = React.useState<any | null>(null);
if (status === "publish") { const [countDonatur, setCountDonatur] = React.useState(0);
return MainColor.green; const [isLoading, setIsLoading] = React.useState(false);
} else if (status === "review") {
return MainColor.orange; useFocusEffect(
} else if (status === "reject") { React.useCallback(() => {
return MainColor.red; onLoadData();
} else { }, [id])
return MainColor.placeholder; );
const onLoadData = async () => {
try {
const response = await apiAdminDonationDetailById({
id: id as string,
});
if (response.success) {
setData(response.data.donasi);
setCountDonatur(response.data.donatur);
}
} catch (error) {
console.log("[ERROR]", error);
setData(null);
} }
}; };
const listData = [ const listData = [
{ {
label: "Penggalang Dana", label: "Penggalang Dana",
value: `Bagas Banuna ${id}`, value: (data && data?.Author?.username) || "-",
}, },
{ {
label: "Judul", label: "Judul",
value: `Donasi Lorem ipsum dolor sit amet, consectetur adipisicing elit.`, value: (data && data?.title) || "-",
}, },
{ {
label: "Status", label: "Status",
value: ( value:
<BadgeCustom color={colorBadge()}> data && data?.DonasiMaster_Status?.name ? (
{_.startCase(status as string)} <BadgeCustom
</BadgeCustom> color={colorBadgeStatus({
), status: data?.DonasiMaster_Status?.name,
})}
>
{_.startCase(data?.DonasiMaster_Status?.name)}
</BadgeCustom>
) : (
"-"
),
}, },
{ {
label: "Durasi", label: "Durasi",
value: "30 Hari", value: (data && data?.DonasiMaster_Durasi?.name) + " hari" || "-",
}, },
{ {
label: "Target Dana", label: "Target Dana",
value: "Rp 10.000.000", value:
data && data?.target
? `Rp. ${formatCurrencyDisplay(data?.target)}`
: "-",
}, },
{ {
label: "Kategori", label: "Kategori",
value: "Kategori Donasi", value: (data && data?.DonasiMaster_Ketegori?.name) || "-",
}, },
// {
// label: "Total Donatur",
// value: "-",
// },
// {
// label: "Progress",
// value: "0 %",
// },
// {
// label: "Dana Terkumpul",
// value: "Rp 0",
// },
]; ];
const listPencarianDana = [ const listPencarianDana = [
{ {
label: "Total Dana Dicairkan", label: "Total Dana Dicairkan",
value: "Rp 0", value: `Rp ${(data && formatCurrencyDisplay(data?.totalPencairan)) || 0}`,
}, },
{ {
label: "Sisa Dana", label: "Sisa Dana Masuk",
value: "Rp 0", value: `Rp ${
(data &&
formatCurrencyDisplay(data?.terkumpul - data?.totalPencairan)) ||
0
}`,
}, },
{ {
label: "Akumulasi Pencairan", label: "Akumulasi Pencairan",
value: "0 kali", value: `${(data && data?.akumulasiPencairan) || 0} kali`,
}, },
{ {
label: "Bank Tujuan", label: "Bank Tujuan",
value: "BNI", value: (data && data?.namaBank) || "-",
}, },
{ {
label: "Nomor Rekening", label: "Nomor Rekening",
value: "123456789", value: (data && data?.rekening) || "-",
}, },
]; ];
const handleReport = async ({
changeStatus,
}: {
changeStatus: "publish" | "review" | "reject";
}) => {
try {
setIsLoading(true);
const response = await funUpdateStatusDonation({
id: id as string,
changeStatus,
data: data,
});
if (!response.success) {
Toast.show({
type: "error",
text1: "Update status gagal",
});
return;
}
Toast.show({
type: "success",
text1: "Update status berhasil",
});
router.back();
} catch (error) {
console.log("[ERROR]", error);
} finally {
setIsLoading(false);
}
};
const rightComponent = ( const rightComponent = (
<ActionIcon <ActionIcon
icon={<IconDot size={ICON_SIZE_BUTTON} />} icon={<IconDot size={ICON_SIZE_BUTTON} />}
@@ -118,8 +175,6 @@ export default function AdminDonationDetail() {
/> />
); );
return ( return (
<> <>
<ViewWrapper <ViewWrapper
@@ -147,11 +202,20 @@ export default function AdminDonationDetail() {
/> />
))} ))}
</StackCustom> </StackCustom>
<ButtonCustom <ButtonCustom
iconLeft={ iconLeft={
<Ionicons name="cash-outline" size={ICON_SIZE_BUTTON} /> <Ionicons name="cash-outline" size={ICON_SIZE_BUTTON} />
} }
disabled={data?.terkumpul - data?.totalPencairan <= 0}
onPress={() => { onPress={() => {
if (data?.terkumpul - data?.totalPencairan <= 0) {
Toast.show({
type: "error",
text1: "Tidak ada dana yang tersisa",
});
return;
}
router.push(`/admin/donation/${id}/disbursement-of-funds`); router.push(`/admin/donation/${id}/disbursement-of-funds`);
}} }}
> >
@@ -161,16 +225,32 @@ export default function AdminDonationDetail() {
</BaseBox> </BaseBox>
<BaseBox> <BaseBox>
<ProgressCustom size="lg" /> <ProgressCustom
size="lg"
value={Number(data?.progres) || 0}
showLabel={true}
label={data?.progres + "%"}
animated
color="primary"
/>
<Spacing /> <Spacing />
<StackCustom gap={"xs"}> <StackCustom gap={"xs"}>
<GridDetail_4_8 <GridDetail_4_8
label={<TextCustom bold>Jumlah Donatur</TextCustom>} label={<TextCustom bold>Jumlah Donatur</TextCustom>}
value={<TextCustom>0 orang</TextCustom>} value={
<TextCustom>
{countDonatur ? countDonatur : 0} orang
</TextCustom>
}
/> />
<GridDetail_4_8 <GridDetail_4_8
label={<TextCustom bold>Dana Terkumpul</TextCustom>} label={<TextCustom bold>Dana Terkumpul</TextCustom>}
value={<TextCustom>Rp 0</TextCustom>} value={
<TextCustom>
Rp {formatCurrencyDisplay(data?.terkumpul || 0)}
</TextCustom>
}
/> />
</StackCustom> </StackCustom>
</BaseBox> </BaseBox>
@@ -179,7 +259,7 @@ export default function AdminDonationDetail() {
<BaseBox> <BaseBox>
<StackCustom> <StackCustom>
<DummyLandscapeImage /> <DummyLandscapeImage imageId={data?.imageId || ""} />
{listData.map((item, i) => ( {listData.map((item, i) => (
<GridDetail_4_8 <GridDetail_4_8
key={i} key={i}
@@ -190,27 +270,33 @@ export default function AdminDonationDetail() {
</StackCustom> </StackCustom>
</BaseBox> </BaseBox>
<AdminDonation_BoxOfDonationStory data={data?.CeritaDonasi as any} />
{data &&
data?.catatan &&
(status === "review" || status === "reject") && (
<ReportBox text={data?.catatan} />
)}
{status === "review" && ( {status === "review" && (
<StackCustom> <StackCustom>
<AdminDonation_BoxOfDonationStory />
<AdminButtonReview <AdminButtonReview
isLoading={isLoading}
onPublish={() => { onPublish={() => {
AlertDefaultSystem({ AlertDefaultSystem({
title: "Publish", title: "Publish",
message: "Apakah anda yakin ingin mempublikasikan data ini?", message: "Apakah anda yakin ingin mempublikasikan data ini?",
textLeft: "Batal", textLeft: "Batal",
textRight: "Ya", textRight: "Ya",
onPressLeft: () => {
router.back();
},
onPressRight: () => { onPressRight: () => {
router.back(); handleReport({ changeStatus: "publish" });
}, },
}); });
}} }}
onReject={() => { onReject={() => {
router.push(`/admin/donation/${id}/reject-input`); router.push(
`/admin/donation/${id}/reject-input?status=${status}`
);
}} }}
/> />
</StackCustom> </StackCustom>
@@ -218,12 +304,12 @@ export default function AdminDonationDetail() {
{status === "reject" && ( {status === "reject" && (
<StackCustom> <StackCustom>
<AdminDonation_BoxOfDonationStory />
<AdminButtonReject <AdminButtonReject
title="Tambah Catatan" title="Tambah Catatan"
onReject={() => { onReject={() => {
router.push(`/admin/donation/${id}/reject-input`); router.push(
`/admin/donation/${id}/reject-input?status=${status}`
);
}} }}
/> />
</StackCustom> </StackCustom>

View File

@@ -9,51 +9,162 @@ import {
} from "@/components"; } from "@/components";
import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButtonAntTitle"; import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButtonAntTitle";
import { GridDetail_4_8 } from "@/components/_ShareComponent/GridDetail_4_8"; import { GridDetail_4_8 } from "@/components/_ShareComponent/GridDetail_4_8";
import { MainColor } from "@/constants/color-palet"; import {
import dayjs from "dayjs"; apiAdminDonationInvoiceDetailById,
import { router, useLocalSearchParams } from "expo-router"; apiAdminDonationInvoiceUpdateById,
} from "@/service/api-admin/api-admin-donation";
import { colorBadgeTransaction } from "@/utils/colorBadge";
import { dateTimeView } from "@/utils/dateTimeView";
import { formatCurrencyDisplay } from "@/utils/formatCurrencyDisplay";
import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
import _ from "lodash";
import { useCallback, useState } from "react";
import Toast from "react-native-toast-message";
export default function AdminDonasiTransactionDetail() { export default function AdminDonasiTransactionDetail() {
const { id } = useLocalSearchParams(); const { id, status } = useLocalSearchParams();
console.log("[STATUS]", id, status);
const buttonAction = ( const [data, setData] = useState<any | null>(null);
<BoxButtonOnFooter> const [isLoading, setLoading] = useState(false);
<ButtonCustom onPress={() => router.back()}>Terima</ButtonCustom>
</BoxButtonOnFooter> useFocusEffect(
useCallback(() => {
onLoadData();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [id])
); );
const onLoadData = async () => {
try {
const response = await apiAdminDonationInvoiceDetailById({
id: id as string,
});
console.log("[GET INVOICE BY ID]", JSON.stringify(response, null, 2));
if (response.success) {
setData(response.data);
}
} catch (error) {
console.log("[ERROR]", error);
}
};
const handlerSubmit = async () => {
try {
setLoading(true);
const newData = {
donationId: data?.donasiId,
nominal: data?.nominal,
};
const response = await apiAdminDonationInvoiceUpdateById({
id: id as string,
data: newData,
status: "berhasil",
});
console.log("[UPDATE INVOICE]", JSON.stringify(response, null, 2));
if (!response.success) {
Toast.show({
type: "error",
text1: response.message,
});
return;
}
Toast.show({
type: "success",
text1: response.message,
});
router.back();
} catch (error) {
console.log("[ERROR]", error);
} finally {
setLoading(false);
}
};
const buttonAction = () => {
if (data && data?.DonasiMaster_StatusInvoice?.name === "Menunggu") {
return null;
}
if (data && data?.DonasiMaster_StatusInvoice?.name === "Proses") {
return (
<BoxButtonOnFooter>
<ButtonCustom
isLoading={isLoading}
onPress={() => {
handlerSubmit();
}}
>
Terima donasi
</ButtonCustom>
</BoxButtonOnFooter>
);
}
return (
<BoxButtonOnFooter>
<ButtonCustom disabled>
{data?.DonasiMaster_StatusInvoice?.name}
</ButtonCustom>
</BoxButtonOnFooter>
);
};
const listData = [ const listData = [
{ {
label: "Donatur", label: "Donatur",
value: "Bagas Banuna", value: (data && data?.Author?.username) || "-",
}, },
{ {
label: "Bank", label: "Bank",
value: "BCA", value: (data && data?.MasterBank?.namaBank) || "-",
}, },
{ {
label: "Jumlah Donasi", label: "Jumlah Donasi",
value: "Rp. 1.000.000", value: `Rp. ${
(data && data?.nominal && formatCurrencyDisplay(data?.nominal)) || "-"
}`,
}, },
{ {
label: "Status", label: "Status",
value: <BadgeCustom color={MainColor.green}>Berhasil</BadgeCustom>, value:
(data && data?.DonasiMaster_StatusInvoice?.name && (
<BadgeCustom
color={colorBadgeTransaction({
status: data?.DonasiMaster_StatusInvoice?.name as any,
})}
>
{_.startCase(
(data?.DonasiMaster_StatusInvoice?.name as any) || "-"
)}
</BadgeCustom>
)) ||
"-",
}, },
{ {
label: "Tanggal", label: "Tanggal",
value: dayjs().format("DD-MM-YYYY HH:mm:ss"), value: (data && dateTimeView({ date: data?.createdAt })) || "-",
}, },
{ {
label: "Bukti Transfer", label: "Bukti Transfer",
value: ( value:
<ButtonCustom (data && data?.imageId && (
onPress={() => <ButtonCustom
router.push(`/(application)/(image)/preview-image/${id}`) onPress={() =>
} router.push(
> `/(application)/(image)/preview-image/${data?.imageId}`
Cek )
</ButtonCustom> }
), >
Cek
</ButtonCustom>
)) ||
"-",
}, },
]; ];
@@ -61,7 +172,7 @@ export default function AdminDonasiTransactionDetail() {
<> <>
<ViewWrapper <ViewWrapper
headerComponent={<AdminBackButtonAntTitle title="Detail Transaksi" />} headerComponent={<AdminBackButtonAntTitle title="Detail Transaksi" />}
footerComponent={buttonAction} footerComponent={buttonAction()}
> >
<BaseBox> <BaseBox>
<StackCustom> <StackCustom>

View File

@@ -1,3 +1,4 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { import {
BaseBox, BaseBox,
ButtonCustom, ButtonCustom,
@@ -7,27 +8,53 @@ import {
} from "@/components"; } from "@/components";
import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButtonAntTitle"; import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButtonAntTitle";
import { GridDetail_4_8 } from "@/components/_ShareComponent/GridDetail_4_8"; import { GridDetail_4_8 } from "@/components/_ShareComponent/GridDetail_4_8";
import dayjs from "dayjs"; import { apiAdminDonationDisbursementOfFundsListById } from "@/service/api-admin/api-admin-donation";
import { router, useLocalSearchParams } from "expo-router"; import { dateTimeView } from "@/utils/dateTimeView";
import { formatCurrencyDisplay } from "@/utils/formatCurrencyDisplay";
import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
import React, { useCallback } from "react";
export default function AdminDonationDetailDisbursementOfFunds() { export default function AdminDonationDetailDisbursementOfFunds() {
const { id } = useLocalSearchParams(); const { id } = useLocalSearchParams();
const [data, setData] = React.useState<any | null>(null);
useFocusEffect(
useCallback(() => {
onLoadData();
}, [id])
);
const onLoadData = async () => {
try {
const response = await apiAdminDonationDisbursementOfFundsListById({
id: id as string,
category: "get-one",
});
if (response.success) {
setData(response.data);
}
} catch (error) {
console.log("[ERROR]", error);
}
};
const listData = [ const listData = [
{ {
label: "Nominal", label: "Nominal",
value: "Rp 1.000.000", value: `Rp ${(data && formatCurrencyDisplay(data?.nominalCair)) || 0}`,
}, },
{ {
label: "Tanggal", label: "Tanggal",
value: dayjs().format("DD-MM-YYYY HH:mm"), value: dateTimeView({ date: data?.createdAt }),
}, },
{ {
label: "Judul", label: "Judul",
value: `Judul Pencairan Dana ${id}`, value: (data && data?.title) || "-",
}, },
{ {
label: "Deskripsi", label: "Deskripsi",
value: `Lorem ipsum dolor sit amet consectetur adipisicing elit. Itaque velit eos facere a dicta nemo repellendus harum laboriosam quos, earum reprehenderit. Nisi sapiente, quo earum quis alias ullam temporibus quidem.`, value: (data && data?.deskripsi) || "-",
}, },
]; ];
return ( return (
@@ -39,7 +66,7 @@ export default function AdminDonationDetailDisbursementOfFunds() {
> >
<BaseBox> <BaseBox>
<StackCustom> <StackCustom>
{listData.map((item, index) => ( {listData?.map((item, index) => (
<GridDetail_4_8 <GridDetail_4_8
key={index} key={index}
label={<TextCustom bold>{item.label}</TextCustom>} label={<TextCustom bold>{item.label}</TextCustom>}
@@ -51,7 +78,7 @@ export default function AdminDonationDetailDisbursementOfFunds() {
<ButtonCustom <ButtonCustom
onPress={() => onPress={() =>
router.push(`/(application)/(image)/preview-image/${id}`) router.push(`/(application)/(image)/preview-image/${data?.imageId}`)
} }
> >
Cek Bukti Transaksi Cek Bukti Transaksi

View File

@@ -1,3 +1,4 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { import {
BaseBox, BaseBox,
BoxButtonOnFooter, BoxButtonOnFooter,
@@ -12,15 +13,122 @@ import {
ViewWrapper, ViewWrapper,
} from "@/components"; } from "@/components";
import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButtonAntTitle"; import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButtonAntTitle";
import { router, useLocalSearchParams } from "expo-router"; import DIRECTORY_ID from "@/constants/directory-id";
import { apiAdminDonationDetailById, apiAdminDonationDisbursementOfFundsCreated } from "@/service/api-admin/api-admin-donation";
import { uploadFileService } from "@/service/upload-service";
import { formatCurrencyDisplay } from "@/utils/formatCurrencyDisplay";
import pickFile from "@/utils/pickFile";
import { Image } from "expo-image";
import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
import React from "react";
import Toast from "react-native-toast-message";
export default function AdminDonationDisbursementOfFunds() { export default function AdminDonationDisbursementOfFunds() {
const { id } = useLocalSearchParams(); const { id } = useLocalSearchParams();
const handleSubmit = (
const [data, setData] = React.useState<any | null>(null);
const [isLoading, setIsLoading] = React.useState(false);
const [value, setValue] = React.useState({
nominalCair: "",
title: "",
deskripsi: "",
});
const [image, setImage] = React.useState<any | null>(null);
useFocusEffect(
React.useCallback(() => {
onLoadData();
}, [id])
);
const onLoadData = async () => {
try {
const response = await apiAdminDonationDetailById({
id: id as string,
});
if (response.success) {
setData(response.data.donasi);
}
} catch (error) {
console.log("[ERROR]", error);
setData(null);
}
};
const handleSubmit = async () => {
if (!image) {
Toast.show({
type: "error",
text1: "Harap upload bukti transfer",
});
return;
}
if (!value.nominalCair || !value.title || !value.deskripsi) {
Toast.show({
type: "error",
text1: "Harap isi semua data",
});
return;
}
try {
setIsLoading(true);
const uploadImage = await uploadFileService({
dirId: DIRECTORY_ID.donasi_bukti_trf_pencairan_dana,
imageUri: image.uri,
});
if (!uploadFileService) {
Toast.show({
type: "error",
text1: "Gagal mengunggah gambar",
});
return;
}
const imageId = uploadImage.data.id;
const newData = {
...value,
imageId: imageId,
};
const response = await apiAdminDonationDisbursementOfFundsCreated({
id: id as string,
data: newData,
});
if (!response.success) {
Toast.show({
type: "error",
text1: response.message,
});
return;
}
Toast.show({
type: "success",
text1: "Pencairan dana berhasil disimpan",
});
router.back();
} catch (error) {
console.log("[ERROR]", error);
} finally {
setIsLoading(false);
}
};
const buttonSubmit = (
<BoxButtonOnFooter> <BoxButtonOnFooter>
<ButtonCustom <ButtonCustom
isLoading={isLoading}
onPress={() => { onPress={() => {
router.back(); handleSubmit();
}} }}
> >
Simpan Simpan
@@ -31,7 +139,7 @@ export default function AdminDonationDisbursementOfFunds() {
return ( return (
<ViewWrapper <ViewWrapper
headerComponent={<AdminBackButtonAntTitle title="Pencairan Dana" />} headerComponent={<AdminBackButtonAntTitle title="Pencairan Dana" />}
footerComponent={handleSubmit} footerComponent={buttonSubmit}
> >
<BaseBox> <BaseBox>
<StackCustom gap="md"> <StackCustom gap="md">
@@ -39,7 +147,7 @@ export default function AdminDonationDisbursementOfFunds() {
Dana Tersisa Dana Tersisa
</TextCustom> </TextCustom>
<TextCustom align="center" bold size="large"> <TextCustom align="center" bold size="large">
Rp 1.000.000 Rp {formatCurrencyDisplay(data?.terkumpul - data?.totalPencairan)}
</TextCustom> </TextCustom>
</StackCustom> </StackCustom>
</BaseBox> </BaseBox>
@@ -56,9 +164,27 @@ export default function AdminDonationDisbursementOfFunds() {
label="Nominal" label="Nominal"
placeholder="0" placeholder="0"
iconLeft={"Rp"} iconLeft={"Rp"}
value={value.nominalCair}
onChangeText={(text) => {
setValue({
...value,
nominalCair: text,
});
}}
/> />
<TextInputCustom required label="Judul" placeholder="Masukan judul" /> <TextInputCustom
required
label="Judul"
placeholder="Masukan judul"
value={value.title}
onChangeText={(text) => {
setValue({
...value,
title: text,
});
}}
/>
<TextAreaCustom <TextAreaCustom
required required
@@ -66,20 +192,37 @@ export default function AdminDonationDisbursementOfFunds() {
placeholder="Masukan deskripsi" placeholder="Masukan deskripsi"
showCount showCount
maxLength={500} maxLength={500}
value={value.deskripsi}
onChangeText={(text) => {
setValue({
...value,
deskripsi: text,
});
}}
/> />
</StackCustom> </StackCustom>
</BaseBox> </BaseBox>
<InformationBox text="Wajib menyertakan bukti transfer" />
<Spacing />
<InformationBox text="Wajib menyertakan bukti transfer" />
<ButtonCenteredOnly <ButtonCenteredOnly
onPress={() => { onPress={() => {
router.push(`/(application)/(image)/take-picture/${id}`); pickFile({
allowedType: "image",
aspectRatio: [9, 16],
setImageUri: (file) => {
setImage(file);
},
});
}} }}
icon="upload" icon="upload"
> >
Upload Upload
</ButtonCenteredOnly> </ButtonCenteredOnly>
<Spacing /> <Spacing />
<Image source={image?.uri} style={{ width: "100%", height: 300 }} />
<Spacing />
</ViewWrapper> </ViewWrapper>
); );
} }

View File

@@ -1,21 +1,54 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { import {
ActionIcon, ActionIcon,
CenterCustom, CenterCustom,
Divider, Divider,
StackCustom, LoaderCustom,
TextCustom, StackCustom,
ViewWrapper TextCustom,
ViewWrapper,
} from "@/components"; } from "@/components";
import { IconView } from "@/components/_Icon/IconComponent"; import { IconView } from "@/components/_Icon/IconComponent";
import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButtonAntTitle"; import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButtonAntTitle";
import { GridViewCustomSpan } from "@/components/_ShareComponent/GridViewCustomSpan"; import { GridViewCustomSpan } from "@/components/_ShareComponent/GridViewCustomSpan";
import { ICON_SIZE_BUTTON } from "@/constants/constans-value"; import { ICON_SIZE_BUTTON } from "@/constants/constans-value";
import { apiAdminDonationDisbursementOfFundsListById } from "@/service/api-admin/api-admin-donation";
import { formatCurrencyDisplay } from "@/utils/formatCurrencyDisplay";
import dayjs from "dayjs"; import dayjs from "dayjs";
import { router, useLocalSearchParams } from "expo-router"; import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
import _ from "lodash";
import React, { useCallback } from "react";
import { View } from "react-native"; import { View } from "react-native";
export default function AdminDonasiListOfDisbursementOfFunds() { export default function AdminDonasiListOfDisbursementOfFunds() {
const { id } = useLocalSearchParams(); const { id } = useLocalSearchParams();
const [listData, setListData] = React.useState<any[] | null>(null);
const [loadData, setLoadData] = React.useState(false);
useFocusEffect(
useCallback(() => {
onLoadData();
}, [id])
);
const onLoadData = async () => {
try {
setLoadData(true);
const response = await apiAdminDonationDisbursementOfFundsListById({
id: id as string,
category: "get-all",
});
if (response.success) {
setListData(response.data);
}
} catch (error) {
console.log("[ERROR]", error);
} finally {
setLoadData(false);
}
};
return ( return (
<> <>
<ViewWrapper <ViewWrapper
@@ -45,36 +78,47 @@ export default function AdminDonasiListOfDisbursementOfFunds() {
/> />
<Divider /> <Divider />
<StackCustom> <StackCustom>
{Array.from({ length: 10 }).map((_, index) => ( {loadData ? (
<View key={index}> <LoaderCustom />
<GridViewCustomSpan ) : _.isEmpty(listData) ? (
span1={3} <TextCustom align="center" color="gray">
span2={5} Belum ada data
span3={4} </TextCustom>
component1={ ) : (
<CenterCustom> listData?.map((item, index) => (
<ActionIcon <View key={index}>
icon={<IconView size={ICON_SIZE_BUTTON} color="black" />} <GridViewCustomSpan
onPress={() => { span1={3}
router.push( span2={5}
`/admin/donation/${id}/detail-disbursement-of-funds` span3={4}
); component1={
}} <CenterCustom>
/> <ActionIcon
</CenterCustom> icon={
} <IconView size={ICON_SIZE_BUTTON} color="black" />
component2={ }
<TextCustom bold align="center" truncate> onPress={() => {
{dayjs() router.push(
.add(index + 1, "day") `/admin/donation/${item?.id}/detail-disbursement-of-funds`
.format("DD-MM-YYYY HH:mm")} );
</TextCustom> }}
} />
component3={<TextCustom>Rp. 1.000.000</TextCustom>} </CenterCustom>
/> }
<Divider /> component2={
</View> <TextCustom align="center" truncate>
))} {dayjs(item?.createdAt).format("DD-MM-YYYY")}
</TextCustom>
}
component3={
<TextCustom align="center" truncate>
Rp. {formatCurrencyDisplay(item?.nominalCair)}
</TextCustom>
}
/>
</View>
))
)}
</StackCustom> </StackCustom>
</ViewWrapper> </ViewWrapper>
</> </>

View File

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

View File

@@ -1,3 +1,4 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { import {
AlertDefaultSystem, AlertDefaultSystem,
BoxButtonOnFooter, BoxButtonOnFooter,
@@ -6,15 +7,84 @@ import {
} from "@/components"; } from "@/components";
import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButtonAntTitle"; import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButtonAntTitle";
import AdminButtonReject from "@/components/_ShareComponent/Admin/ButtonReject"; import AdminButtonReject from "@/components/_ShareComponent/Admin/ButtonReject";
import { router, useLocalSearchParams } from "expo-router"; import { funUpdateStatusDonation } from "@/screens/Admin/Donation/funDonationUpdateStatus";
import { useState } from "react"; import {
apiAdminDonationDetailById
} from "@/service/api-admin/api-admin-donation";
import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
import React from "react";
import Toast from "react-native-toast-message";
export default function AdminDonationRejectInput() { export default function AdminDonationRejectInput() {
const { id } = useLocalSearchParams(); const { id, status } = useLocalSearchParams();
const [value, setValue] = useState(id as string);
const [data, setData] = React.useState<any | null>(null);
const [isLoading, setIsLoading] = React.useState(false);
useFocusEffect(
React.useCallback(() => {
onLoadData();
}, [id])
);
const onLoadData = async () => {
try {
const response = await apiAdminDonationDetailById({
id: id as string,
});
if (response.success) {
setData(response.data.catatan);
}
} catch (error) {
console.log("[ERROR]", error);
setData(null);
}
};
const handleReport = async ({
changeStatus,
}: {
changeStatus: "publish" | "review" | "reject";
}) => {
try {
setIsLoading(true);
const response = await funUpdateStatusDonation({
id: id as string,
changeStatus,
data: data,
});
if (!response.success) {
Toast.show({
type: "error",
text1: "Report gagal",
});
return
}
Toast.show({
type: "success",
text1: "Report berhasil",
});
if (status === "review") {
router.replace(`/admin/donation/reject/status`);
} else if (status === "reject") {
router.back();
}
} catch (error) {
console.log("[ERROR]", error);
} finally {
setIsLoading(false);
}
};
const buttonSubmit = ( const buttonSubmit = (
<BoxButtonOnFooter> <BoxButtonOnFooter>
<AdminButtonReject <AdminButtonReject
isLoading={isLoading}
title="Reject" title="Reject"
onReject={() => onReject={() =>
AlertDefaultSystem({ AlertDefaultSystem({
@@ -22,12 +92,9 @@ export default function AdminDonationRejectInput() {
message: "Apakah anda yakin ingin menolak data ini?", message: "Apakah anda yakin ingin menolak data ini?",
textLeft: "Batal", textLeft: "Batal",
textRight: "Ya", textRight: "Ya",
onPressLeft: () => {
router.back();
},
onPressRight: () => { onPressRight: () => {
console.log("value:", value); handleReport({ changeStatus: "reject" });
router.replace(`/admin/donation/reject/status`);
}, },
}) })
} }
@@ -42,8 +109,8 @@ export default function AdminDonationRejectInput() {
headerComponent={<AdminBackButtonAntTitle title="Penolakan Donasi" />} headerComponent={<AdminBackButtonAntTitle title="Penolakan Donasi" />}
> >
<TextAreaCustom <TextAreaCustom
value={value} value={data}
onChangeText={setValue} onChangeText={setData}
placeholder="Masukan alasan" placeholder="Masukan alasan"
required required
showCount showCount

View File

@@ -1,69 +1,116 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { import {
ActionIcon, ActionIcon,
SearchInput, LoaderCustom,
Spacing, SearchInput,
TextCustom, StackCustom,
ViewWrapper TextCustom,
ViewWrapper
} from "@/components"; } from "@/components";
import AdminComp_BoxTitle from "@/components/_ShareComponent/Admin/BoxTitlePage"; import AdminComp_BoxTitle from "@/components/_ShareComponent/Admin/BoxTitlePage";
import AdminTitleTable from "@/components/_ShareComponent/Admin/TableTitle"; import AdminTitleTable from "@/components/_ShareComponent/Admin/TableTitle";
import AdminTableValue from "@/components/_ShareComponent/Admin/TableValue"; import AdminTableValue from "@/components/_ShareComponent/Admin/TableValue";
import AdminTitlePage from "@/components/_ShareComponent/Admin/TitlePage";
import { ICON_SIZE_BUTTON } from "@/constants/constans-value"; import { ICON_SIZE_BUTTON } from "@/constants/constans-value";
import { apiAdminDonation } from "@/service/api-admin/api-admin-donation";
import { Octicons } from "@expo/vector-icons"; import { Octicons } from "@expo/vector-icons";
import { router, useLocalSearchParams } from "expo-router"; import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
import _ from "lodash"; import _ from "lodash";
import { useCallback, useState } from "react";
import { Divider } from "react-native-paper"; import { Divider } from "react-native-paper";
export default function AdminDonationStatus() { export default function AdminDonationStatus() {
const { status } = useLocalSearchParams(); const { status } = useLocalSearchParams();
console.log("[STATUS]", status);
const [data, setData] = useState<any | null>(null);
const [search, setSearch] = useState<string>("");
const [loadData, setLoadData] = useState<boolean>(false);
useFocusEffect(
useCallback(() => {
onLoadData();
}, [status, search])
);
const onLoadData = async () => {
try {
setLoadData(true);
const response = await apiAdminDonation({
category: status as "publish" | "review" | "reject",
search,
});
console.log("[RES]", JSON.stringify(response, null, 2));
if (response.success) {
setData(response.data);
}
} catch (error) {
console.log("[ERROR]", error);
setData([]);
} finally {
setLoadData(false);
}
};
const rightComponent = ( const rightComponent = (
<SearchInput <SearchInput
containerStyle={{ width: "100%", marginBottom: 0 }} containerStyle={{ width: "100%", marginBottom: 0 }}
placeholder="Cari" placeholder="Cari"
value={search}
onChangeText={(value) => setSearch(value)}
/> />
); );
return ( return (
<> <>
<ViewWrapper <ViewWrapper headerComponent={<AdminTitlePage title="Donasi" />}>
headerComponent={ <StackCustom gap={"sm"}>
<AdminComp_BoxTitle <AdminComp_BoxTitle
title={`Donasi ${_.startCase(status as string)}`} title={`${_.startCase(status as string)}`}
rightComponent={rightComponent} rightComponent={rightComponent}
/> />
} <AdminTitleTable
> title1="Aksi"
<AdminTitleTable title2="Username"
title1="Aksi" title3="Judul Donasi"
title2="Username"
title3="Judul Donasi"
/>
<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/donation/${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>
}
/> />
))} <Divider />
{loadData ? (
<LoaderCustom />
) : _.isEmpty(data) ? (
<TextCustom align="center" size="small" color="gray">
Belum ada data
</TextCustom>
) : (
data?.map((item: any, index: number) => (
<AdminTableValue
key={index}
value1={
<ActionIcon
icon={
<Octicons
name="eye"
size={ICON_SIZE_BUTTON}
color="black"
/>
}
onPress={() => {
router.push(`/admin/donation/${item.id}/${status}`);
}}
/>
}
value2={<TextCustom truncate={1}>{item?.Author?.username || "-"}</TextCustom>}
value3={
<TextCustom truncate={2}>
{item?.title || "-"}
</TextCustom>
}
/>
))
)}
</StackCustom>
</ViewWrapper> </ViewWrapper>
</> </>
); );

View File

@@ -1,15 +1,67 @@
import { Spacing, StackCustom, ViewWrapper } from "@/components"; import { Spacing, StackCustom, ViewWrapper } from "@/components";
import { import {
IconList, IconList,
IconPublish, IconPublish,
IconReject, IconReject,
IconReview, IconReview,
} from "@/components/_Icon/IconComponent"; } from "@/components/_Icon/IconComponent";
import AdminComp_BoxDashboard from "@/components/_ShareComponent/Admin/BoxDashboard"; import AdminComp_BoxDashboard from "@/components/_ShareComponent/Admin/BoxDashboard";
import AdminTitlePage from "@/components/_ShareComponent/Admin/TitlePage"; import AdminTitlePage from "@/components/_ShareComponent/Admin/TitlePage";
import { MainColor } from "@/constants/color-palet"; import { MainColor } from "@/constants/color-palet";
import { apiAdminDonation } from "@/service/api-admin/api-admin-donation";
import { useFocusEffect } from "expo-router";
import { useState, useCallback } from "react";
export default function AdminDonation() { export default function AdminDonation() {
const [data, setData] = useState<any | null>(null);
useFocusEffect(
useCallback(() => {
onLoadData();
}, [])
);
const onLoadData = async () => {
try {
const response = await apiAdminDonation({
category: "dashboard",
});
console.log("[RES]", JSON.stringify(response, null, 2));
if (response.success) {
setData(response.data);
}
} catch (error) {
console.log("[ERROR]", error);
setData([]);
}
};
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} />,
},
{
label: "Kategori",
value: (data && data.categoryDonation) || 0,
icon: <IconList size={25} color={MainColor.white_gray} />,
},
];
return ( return (
<> <>
<ViewWrapper> <ViewWrapper>
@@ -24,26 +76,3 @@ export default function AdminDonation() {
</> </>
); );
} }
const listData = [
{
label: "Publish",
value: 4,
icon: <IconPublish size={25} color={MainColor.green} />,
},
{
label: "Review",
value: 7,
icon: <IconReview size={25} color={MainColor.orange} />,
},
{
label: "Reject",
value: 5,
icon: <IconReject size={25} color={MainColor.red} />,
},
{
label: "Kategori",
value: 4,
icon: <IconList size={25} color={MainColor.white_gray} />,
},
];

View File

@@ -1,15 +1,16 @@
/* eslint-disable react-hooks/exhaustive-deps */ /* eslint-disable react-hooks/exhaustive-deps */
import { import {
ActionIcon, ActionIcon,
AlertDefaultSystem, AlertDefaultSystem,
BadgeCustom, BadgeCustom,
BaseBox, BaseBox,
DrawerCustom, DrawerCustom,
MenuDrawerDynamicGrid, LoaderCustom,
Spacing, MenuDrawerDynamicGrid,
StackCustom, Spacing,
TextCustom, StackCustom,
ViewWrapper, TextCustom,
ViewWrapper,
} from "@/components"; } from "@/components";
import { IconDot, IconList } from "@/components/_Icon/IconComponent"; import { IconDot, IconList } from "@/components/_Icon/IconComponent";
import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButtonAntTitle"; import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButtonAntTitle";
@@ -22,24 +23,21 @@ import { useAuth } from "@/hooks/use-auth";
import { funUpdateStatusEvent } from "@/screens/Admin/Event/funUpdateStatus"; import { funUpdateStatusEvent } from "@/screens/Admin/Event/funUpdateStatus";
import { apiAdminEventById } from "@/service/api-admin/api-admin-event"; import { apiAdminEventById } from "@/service/api-admin/api-admin-event";
import { DEEP_LINK_URL } from "@/service/api-config"; import { DEEP_LINK_URL } from "@/service/api-config";
import { colorBadge } from "@/utils/colorBadge"; import { colorBadgeStatus } from "@/utils/colorBadge";
import { dateTimeView } from "@/utils/dateTimeView"; import { dateTimeView } from "@/utils/dateTimeView";
import { router, useFocusEffect, useLocalSearchParams } from "expo-router"; import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
import _ from "lodash"; import _ from "lodash";
import React, { useCallback } from "react"; import React, { useCallback } from "react";
import QRCode from "react-native-qrcode-svg"; import QRCode from "react-native-qrcode-svg";
import Toast from "react-native-toast-message";
export default function AdminEventDetail() { export default function AdminEventDetail() {
const { user } = useAuth(); const { user } = useAuth();
const { id, status } = useLocalSearchParams(); const { id, status } = useLocalSearchParams();
console.log("[ID QRCODE]", id);
console.log("[STATUS Detail]", status);
const [openDrawer, setOpenDrawer] = React.useState(false); const [openDrawer, setOpenDrawer] = React.useState(false);
const newURL = DEEP_LINK_URL
console.log("[DEEP LINK URL]", newURL);
const [data, setData] = React.useState<any | null>(null); const [data, setData] = React.useState<any | null>(null);
const [loadData, setLoadData] = React.useState(false);
const deepLinkURL = `${DEEP_LINK_URL}/--/event/${id}/confirmation?userId=${user?.id}`; const deepLinkURL = `${DEEP_LINK_URL}/--/event/${id}/confirmation?userId=${user?.id}`;
useFocusEffect( useFocusEffect(
useCallback(() => { useCallback(() => {
@@ -48,17 +46,18 @@ export default function AdminEventDetail() {
); );
const onLoadData = async () => { const onLoadData = async () => {
try { try {
setLoadData(true);
const response = await apiAdminEventById({ const response = await apiAdminEventById({
id: id as string, id: id as string,
}); });
// console.log(`[RES DATA BY ID: ${id}]`, JSON.stringify(response, null, 2));
if (response.success) { if (response.success) {
setData(response.data); setData(response.data);
} }
} catch (error) { } catch (error) {
console.log("[ERROR]", error); console.log("[ERROR]", error);
} finally {
setLoadData(false);
} }
}; };
@@ -75,7 +74,7 @@ export default function AdminEventDetail() {
label: "Status", label: "Status",
value: value:
(data && ( (data && (
<BadgeCustom color={colorBadge({ status: status as string })}> <BadgeCustom color={colorBadgeStatus({ status: status as string })}>
{_.startCase(status as string)} {_.startCase(status as string)}
</BadgeCustom> </BadgeCustom>
)) || )) ||
@@ -124,11 +123,19 @@ export default function AdminEventDetail() {
changeStatus: "publish", changeStatus: "publish",
}); });
console.log("[RES PUBLISH]", JSON.stringify(response, null, 2)); if (!response.success) {
Toast.show({
if (response.success) { type: "error",
router.back(); text1: "Gagal mempublikasikan event",
});
return;
} }
Toast.show({
type: "success",
text1: "Event berhasil dipublikasikan",
});
router.back();
} catch (error) { } catch (error) {
console.log("[ERROR]", error); console.log("[ERROR]", error);
} }
@@ -170,15 +177,19 @@ export default function AdminEventDetail() {
<BaseBox> <BaseBox>
<StackCustom style={{ alignItems: "center" }}> <StackCustom style={{ alignItems: "center" }}>
<TextCustom bold>QR Code Event</TextCustom> <TextCustom bold>QR Code Event</TextCustom>
<QRCode {loadData ? (
value={deepLinkURL} <LoaderCustom />
size={200} ) : (
// logo={require("@/assets/images/logo-hipmi.png")} <QRCode
// logoSize={70} value={deepLinkURL}
// logoBackgroundColor="transparent" size={200}
// logoBorderRadius={50} // logo={require("@/assets/images/logo-hipmi.png")}
// color="black" // logoSize={70}
/> // logoBackgroundColor="transparent"
// logoBorderRadius={50}
// color="black"
/>
)}
</StackCustom> </StackCustom>
</BaseBox> </BaseBox>
)} )}

View File

@@ -1,41 +1,81 @@
import { BadgeCustom, BaseBox, Grid, TextCustom, ViewWrapper } from "@/components"; /* eslint-disable react-hooks/exhaustive-deps */
import {
BadgeCustom,
BaseBox,
Grid,
LoaderCustom,
StackCustom,
TextCustom,
ViewWrapper,
} from "@/components";
import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButtonAntTitle"; import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButtonAntTitle";
import { MainColor } from "@/constants/color-palet"; import { apiAdminEventListOfParticipants } from "@/service/api-admin/api-admin-event";
import { useFocusEffect, useLocalSearchParams } from "expo-router";
import _ from "lodash";
import { useCallback, useState } from "react";
export default function AdminEventListOfParticipants() { export default function AdminEventListOfParticipants() {
const { id } = useLocalSearchParams();
const [listData, setListData] = useState<any[] | null>(null);
const [loadData, setLoadData] = useState(false);
const isPresent = ({id}: {id: number}) => { useFocusEffect(
const check = id % 3 * 3; useCallback(() => {
if (check === 0) { onLoadData();
return true; }, [id])
} else { );
return false;
} const onLoadData = async () => {
try {
setLoadData(true);
const response = await apiAdminEventListOfParticipants({
id: id as string,
});
if (response.success) {
setListData(response.data);
}
} catch (error) {
console.log("[ERROR]", error);
} finally {
setLoadData(false);
} }
};
return ( return (
<> <>
<ViewWrapper <ViewWrapper
headerComponent={<AdminBackButtonAntTitle title="Daftar Peserta" />} headerComponent={<AdminBackButtonAntTitle title="Daftar Peserta" />}
> >
{Array.from({ length: 10 }).map((item, index) => ( {loadData ? (
<BaseBox key={index}> <LoaderCustom />
<Grid> ) : _.isEmpty(listData) ? (
<Grid.Col span={6}> <TextCustom align="center" color="gray">
<TextCustom bold>Username {index + 1}</TextCustom> Belum ada peserta
<TextCustom>+6282123456789</TextCustom> </TextCustom>
</Grid.Col> ) : (
<Grid.Col span={6} style={{ justifyContent: "center" }}> listData?.map((item: any, index: number) => (
<BadgeCustom <BaseBox key={index}>
style={{ alignSelf: "flex-end" }} <Grid>
color={isPresent({id: index}) ? MainColor.green : MainColor.red} <Grid.Col span={6}>
> <StackCustom gap={"sm"}>
{isPresent({id: index}) ? "Hadir" : "Tidak Hadir"} <TextCustom bold truncate>{item?.User?.username}</TextCustom>
</BadgeCustom> <TextCustom>+{item?.User?.nomor}</TextCustom>
</Grid.Col> </StackCustom>
</Grid> </Grid.Col>
</BaseBox> <Grid.Col span={6} style={{ justifyContent: "center" }}>
))} <BadgeCustom
style={{ alignSelf: "flex-end" }}
color={item?.isPresent ? "green" : "red"}
>
{item?.isPresent ? "Hadir" : "Tidak Hadir"}
</BadgeCustom>
</Grid.Col>
</Grid>
</BaseBox>
))
)}
</ViewWrapper> </ViewWrapper>
</> </>
); );

View File

@@ -5,13 +5,48 @@ import {
ViewWrapper, ViewWrapper,
} from "@/components"; } from "@/components";
import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButtonAntTitle"; import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButtonAntTitle";
import { apiEventCreateTypeOfEvent } from "@/service/api-admin/api-master-admin";
import { useRouter } from "expo-router"; import { useRouter } from "expo-router";
import { useState } from "react";
import Toast from "react-native-toast-message";
export default function AdminEventTypeOfEventCreate() { export default function AdminEventTypeOfEventCreate() {
const router = useRouter(); const router = useRouter();
const [value, setValue] = useState("");
const [isLoading, setLoading] = useState<boolean>(false);
const handlerSubmit = async () => {
try {
setLoading(true);
const response = await apiEventCreateTypeOfEvent({
data: value,
});
if (!response.success) {
Toast.show({
type: "error",
text1: "Gagal menambahkan tipe acara",
});
return;
}
Toast.show({
type: "success",
text1: "Berhasil menambahkan tipe acara",
});
router.back();
} catch (error) {
console.log("[ERROR CREATE TYPE EVENT]", error);
} finally {
setLoading(false);
}
};
const buttonSubmit = ( const buttonSubmit = (
<BoxButtonOnFooter> <BoxButtonOnFooter>
<ButtonCustom onPress={() => router.back()}>Simpan</ButtonCustom> <ButtonCustom isLoading={isLoading} onPress={() => handlerSubmit()}>
Simpan
</ButtonCustom>
</BoxButtonOnFooter> </BoxButtonOnFooter>
); );
return ( return (
@@ -20,7 +55,11 @@ export default function AdminEventTypeOfEventCreate() {
headerComponent={<AdminBackButtonAntTitle title="Tambah Tipe Acara" />} headerComponent={<AdminBackButtonAntTitle title="Tambah Tipe Acara" />}
footerComponent={buttonSubmit} footerComponent={buttonSubmit}
> >
<TextInputCustom placeholder="Masukkan Tipe Acara" /> <TextInputCustom
placeholder="Masukkan Tipe Acara"
value={value}
onChangeText={setValue}
/>
</ViewWrapper> </ViewWrapper>
</> </>
); );

View File

@@ -1,23 +1,53 @@
import { import {
ActionIcon, ActionIcon,
BaseBox, BadgeCustom,
CenterCustom, CenterCustom,
LoaderCustom,
Spacing, Spacing,
StackCustom, StackCustom,
TextCustom, TextCustom,
ViewWrapper, ViewWrapper
} from "@/components"; } from "@/components";
import { IconEdit } from "@/components/_Icon"; import { IconEdit } from "@/components/_Icon";
import AdminActionIconPlus from "@/components/_ShareComponent/Admin/ActionIconPlus"; import AdminActionIconPlus from "@/components/_ShareComponent/Admin/ActionIconPlus";
import AdminComp_BoxTitle from "@/components/_ShareComponent/Admin/BoxTitlePage"; import AdminComp_BoxTitle from "@/components/_ShareComponent/Admin/BoxTitlePage";
import AdminTitlePage from "@/components/_ShareComponent/Admin/TitlePage"; import AdminTitlePage from "@/components/_ShareComponent/Admin/TitlePage";
import { GridDetail_4_8 } from "@/components/_ShareComponent/GridDetail_4_8"; import { GridViewCustomSpan } from "@/components/_ShareComponent/GridViewCustomSpan";
import { ICON_SIZE_BUTTON } from "@/constants/constans-value"; import { ICON_SIZE_BUTTON } from "@/constants/constans-value";
import { router } from "expo-router"; import { apiAdminMasterTypeOfEvent } from "@/service/api-admin/api-master-admin";
import { colorActivationForBadge } from "@/utils/colorActivationForBadge";
import { router, useFocusEffect } from "expo-router";
import _ from "lodash";
import { useCallback, useState } from "react";
import { View } from "react-native"; import { View } from "react-native";
import { Divider } from "react-native-paper"; import { Divider } from "react-native-paper";
export default function AdminEventTypeOfEvent() { export default function AdminEventTypeOfEvent() {
const [listData, setListData] = useState<any[] | null>(null);
const [loadData, setLoadData] = useState<boolean>(false);
useFocusEffect(
useCallback(() => {
onLoadData();
}, [])
);
const onLoadData = async () => {
try {
setLoadData(true);
const response = await apiAdminMasterTypeOfEvent();
if (response.success) {
setListData(response.data);
}
} catch (error) {
console.log("[ERROR]",error);
setListData([]);
} finally {
setLoadData(false);
}
};
return ( return (
<> <>
<ViewWrapper headerComponent={<AdminTitlePage title="Event" />}> <ViewWrapper headerComponent={<AdminTitlePage title="Event" />}>
@@ -32,73 +62,68 @@ export default function AdminEventTypeOfEvent() {
} }
/> />
<BaseBox> <>
<GridDetail_4_8 <GridViewCustomSpan
label={ span1={2}
span2={5}
span3={5}
component1={
<TextCustom bold align="center"> <TextCustom bold align="center">
Aksi Aksi
</TextCustom> </TextCustom>
} }
value={<TextCustom bold>Tipe Acara</TextCustom>} component2={<TextCustom bold align="center">Status</TextCustom>}
component3={<TextCustom bold>Tipe Acara</TextCustom>}
/> />
<Divider /> <Divider />
<Spacing /> <Spacing />
<StackCustom> <StackCustom>
{listData.map((item, index) => ( {loadData ? (
<View key={index}> <LoaderCustom />
<GridDetail_4_8 ) : _.isEmpty(listData) ? (
label={ <TextCustom align="center" color="gray">
<CenterCustom> Belum ada data
<ActionIcon </TextCustom>
icon={ ) : (
<IconEdit size={ICON_SIZE_BUTTON} color="black" /> listData?.map((item, index) => (
} <View key={index}>
onPress={() => { <GridViewCustomSpan
router.push(`/admin/event/type-update?id=${index}`); span1={2}
}} span2={5}
/> span3={5}
</CenterCustom> component1={
} <CenterCustom>
value={<TextCustom bold>{item.label}</TextCustom>} <ActionIcon
/> icon={
<Divider /> <IconEdit size={ICON_SIZE_BUTTON} color="black" />
</View> }
))} onPress={() => {
router.push(`/admin/event/type-update?id=${item.id}`);
}}
/>
</CenterCustom>
}
style2={{ alignItems: "center" }}
component2={
<CenterCustom>
<BadgeCustom
color={colorActivationForBadge({
status: item?.active,
})}
>
{item?.active ? "Aktif" : "Tidak Aktif"}
</BadgeCustom>
</CenterCustom>
}
component3={<TextCustom >{item.name}</TextCustom>}
/>
</View>
))
)}
</StackCustom> </StackCustom>
</BaseBox> </>
</ViewWrapper> </ViewWrapper>
</> </>
); );
} }
const listData = [
{
label: "Seminar",
value: "seminar",
},
{
label: "Workshop",
value: "workshop",
},
{
label: "Konferensi",
value: "konferensi",
},
{
label: "Lomba",
value: "lomba",
},
{
label: "Pameran",
value: "pameran",
},
{
label: "Pesta",
value: "pesta",
},
{
label: "Pertandingan",
value: "pertandingan",
},
];

View File

@@ -1,20 +1,91 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { import {
BoxButtonOnFooter, BoxButtonOnFooter,
ButtonCustom, ButtonCustom,
Spacing,
TextCustom,
TextInputCustom, TextInputCustom,
ViewWrapper, ViewWrapper,
} from "@/components"; } from "@/components";
import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButtonAntTitle"; import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButtonAntTitle";
import { useLocalSearchParams, useRouter } from "expo-router"; import { MainColor } from "@/constants/color-palet";
import {
apiAdminMasterTypeOfEventGetOne,
apiAdminMasterTypeOfEventUpdate,
} from "@/service/api-admin/api-master-admin";
import { useFocusEffect, useLocalSearchParams, useRouter } from "expo-router";
import { useCallback, useState } from "react";
import { Switch } from "react-native-paper";
import Toast from "react-native-toast-message";
export default function AdminEventTypeOfEventUpdate() { export default function AdminEventTypeOfEventUpdate() {
const { id } = useLocalSearchParams(); const { id } = useLocalSearchParams();
console.log("id >", id);
const router = useRouter(); const router = useRouter();
const [data, setData] = useState<{ name: string; active: boolean }>({
name: "",
active: false,
});
const [isLoading, setLoading] = useState<boolean>(false);
useFocusEffect(
useCallback(() => {
onLoadData();
}, [id])
);
const onLoadData = async () => {
try {
const response = await apiAdminMasterTypeOfEventGetOne({
id: id as string,
});
if (response.success) {
setData({
name: response.data.name,
active: response.data.active,
});
}
} catch (error) {
console.log("[ERROR UPDATE]", error);
}
};
const handlerSubmit = async () => {
try {
setLoading(true);
const response = await apiAdminMasterTypeOfEventUpdate({
id: id as string,
data: data,
});
if (!response.success) {
Toast.show({
type: "error",
text1: "Gagal mengupdate tipe acara",
});
return;
}
Toast.show({
type: "success",
text1: "Berhasil mengupdate tipe acara",
});
router.back();
} catch (error) {
console.log("[ERROR UPDATE]", error);
} finally {
setLoading(false);
}
};
const buttonSubmit = ( const buttonSubmit = (
<BoxButtonOnFooter> <BoxButtonOnFooter>
<ButtonCustom onPress={() => router.back()}>Update</ButtonCustom> <ButtonCustom isLoading={isLoading} onPress={() => handlerSubmit()}>
Update
</ButtonCustom>
</BoxButtonOnFooter> </BoxButtonOnFooter>
); );
return ( return (
@@ -23,7 +94,19 @@ export default function AdminEventTypeOfEventUpdate() {
headerComponent={<AdminBackButtonAntTitle title="Ubah Tipe Acara" />} headerComponent={<AdminBackButtonAntTitle title="Ubah Tipe Acara" />}
footerComponent={buttonSubmit} footerComponent={buttonSubmit}
> >
<TextInputCustom placeholder="Masukkan Tipe Acara" value="" /> <TextInputCustom
placeholder="Masukkan Tipe Acara"
value={data.name}
onChangeText={(text) => setData({ ...data, name: text })}
/>
<TextCustom>Aktivasi</TextCustom>
<Spacing height={10} />
<Switch
color={MainColor.yellow}
value={data.active}
onValueChange={(value) => setData({ ...data, active: value })}
/>
</ViewWrapper> </ViewWrapper>
</> </>
); );

View File

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

View File

@@ -1,3 +1,4 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { import {
BadgeCustom, BadgeCustom,
BaseBox, BaseBox,
@@ -9,12 +10,38 @@ import {
} from "@/components"; } from "@/components";
import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButtonAntTitle"; import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButtonAntTitle";
import { GridDetail_4_8 } from "@/components/_ShareComponent/GridDetail_4_8"; import { GridDetail_4_8 } from "@/components/_ShareComponent/GridDetail_4_8";
import { MainColor } from "@/constants/color-palet"; import { apiAdminInvestmentGetOneInvoiceById } from "@/service/api-admin/api-admin-investment";
import dayjs from "dayjs"; import { colorBadgeTransaction } from "@/utils/colorBadge";
import { router, useLocalSearchParams } from "expo-router"; import { dateTimeView } from "@/utils/dateTimeView";
import { formatCurrencyDisplay } from "@/utils/formatCurrencyDisplay";
import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
import { useCallback, useState } from "react";
export default function AdminInvestmentTransactionDetail() { export default function AdminInvestmentTransactionDetail() {
const { id } = useLocalSearchParams(); const { id } = useLocalSearchParams();
console.log("[ID]", id);
const [data, setData] = useState<any | null>(null);
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 buttonAction = ( const buttonAction = (
<BoxButtonOnFooter> <BoxButtonOnFooter>
@@ -25,30 +52,41 @@ export default function AdminInvestmentTransactionDetail() {
const listData = [ const listData = [
{ {
label: "Investor", label: "Investor",
value: "Bagas Banuna", value: data?.Author?.username || "-",
}, },
{ {
label: "Bank", label: "Bank",
value: "BCA", value: data?.MasterBank?.namaBank || "-",
}, },
{ {
label: "Jumlah Investasi", label: "Jumlah Investasi",
value: "Rp. 1.000.000", value: `Rp. ${formatCurrencyDisplay(data?.nominal) || "-"}`,
}, },
{ {
label: "Status", 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", label: "Tanggal",
value: dayjs().format("DD-MM-YYYY HH:mm:ss"), value: data && dateTimeView({ date: data?.createdAt }) || "-",
}, },
{ {
label: "Bukti Transfer", label: "Bukti Transfer",
value: ( value: (
<ButtonCustom <ButtonCustom
onPress={() => onPress={() =>
router.push(`/(application)/(image)/preview-image/${id}`) router.push(`/(application)/(image)/preview-image/${data?.imageId}`)
} }
> >
Cek Cek
@@ -60,7 +98,9 @@ export default function AdminInvestmentTransactionDetail() {
return ( return (
<> <>
<ViewWrapper <ViewWrapper
headerComponent={<AdminBackButtonAntTitle title="Detail Transaksi Investor" />} headerComponent={
<AdminBackButtonAntTitle title="Detail Transaksi Investor" />
}
footerComponent={buttonAction} footerComponent={buttonAction}
> >
<BaseBox> <BaseBox>

View File

@@ -1,7 +1,9 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { import {
ActionIcon, ActionIcon,
BadgeCustom, BadgeCustom,
CenterCustom, CenterCustom,
LoaderCustom,
SelectCustom, SelectCustom,
StackCustom, StackCustom,
TextCustom, TextCustom,
@@ -10,23 +12,100 @@ import {
import { IconView } from "@/components/_Icon/IconComponent"; import { IconView } from "@/components/_Icon/IconComponent";
import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButtonAntTitle"; import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButtonAntTitle";
import { GridViewCustomSpan } from "@/components/_ShareComponent/GridViewCustomSpan"; import { GridViewCustomSpan } from "@/components/_ShareComponent/GridViewCustomSpan";
import NoDataText from "@/components/_ShareComponent/NoDataText";
import { MainColor } from "@/constants/color-palet"; import { MainColor } from "@/constants/color-palet";
import { ICON_SIZE_BUTTON } from "@/constants/constans-value"; import { ICON_SIZE_BUTTON } from "@/constants/constans-value";
import { dummyMasterStatusTransaction } from "@/lib/dummy-data/_master/status-transaction"; import { dummyMasterStatusTransaction } from "@/lib/dummy-data/_master/status-transaction";
import { router, useLocalSearchParams } from "expo-router"; import { apiAdminInvestmentListOfInvestor } from "@/service/api-admin/api-admin-investment";
import React from "react"; 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 { View } from "react-native";
import { Divider } from "react-native-paper"; import { Divider } from "react-native-paper";
export default function AdminInvestmentListOfInvestor() { export default function AdminInvestmentListOfInvestor() {
const { id } = useLocalSearchParams(); 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 = ( const searchComponent = (
<View style={{ flexDirection: "row", gap: 5 }}> <View style={{ flexDirection: "row", gap: 5 }}>
<SelectCustom <SelectCustom
placeholder="Pilih status transaksi" placeholder="Pilih status transaksi"
data={dummyMasterStatusTransaction} data={
onChange={(value) => console.log(value)} _.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 }} styleContainer={{ width: "100%", marginBottom: 0 }}
allowClear
/> />
</View> </View>
); );
@@ -40,66 +119,77 @@ export default function AdminInvestmentListOfInvestor() {
return ( return (
<> <>
<ViewWrapper <ViewWrapper headerComponent={headerComponent}>
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 />
<StackCustom> <StackCustom>
{Array.from({ length: 10 }).map((_, index) => ( <GridViewCustomSpan
<View key={index}> span1={3}
<GridViewCustomSpan span2={5}
span1={3} span3={4}
span2={5} component1={
span3={4} <TextCustom bold align="center">
component1={ Aksi
<CenterCustom> </TextCustom>
<ActionIcon }
icon={<IconView size={ICON_SIZE_BUTTON} color="black" />} component2={
onPress={() => { <TextCustom bold align="center">
router.push( Investor
`/admin/investment/${id}/berhasil/transaction-detail` </TextCustom>
); }
}} component3={
/> <TextCustom bold align="center">
</CenterCustom> Status
} </TextCustom>
component2={ }
<TextCustom bold align="center" truncate> />
Bagas Banuna <Divider />
</TextCustom> <StackCustom>
} {loadData ? (
component3={ <LoaderCustom />
<BadgeCustom ) : _.isEmpty(listData) ? (
style={{ alignSelf: "center" }} <NoDataText />
color={MainColor.green} ) : (
> listData?.map((item: any, index: number) => (
Berhasil <View key={index}>
</BadgeCustom> <GridViewCustomSpan
} span1={3}
/> span2={5}
<Divider /> span3={4}
</View> 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> </StackCustom>
</ViewWrapper> </ViewWrapper>
</> </>

View File

@@ -1,3 +1,4 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { import {
AlertDefaultSystem, AlertDefaultSystem,
BoxButtonOnFooter, BoxButtonOnFooter,
@@ -6,15 +7,83 @@ import {
} from "@/components"; } from "@/components";
import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButtonAntTitle"; import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButtonAntTitle";
import AdminButtonReject from "@/components/_ShareComponent/Admin/ButtonReject"; import AdminButtonReject from "@/components/_ShareComponent/Admin/ButtonReject";
import { router, useLocalSearchParams } from "expo-router"; import { apiAdminInvestasiUpdateByStatus, apiAdminInvestmentDetailById } from "@/service/api-admin/api-admin-investment";
import { useState } from "react"; import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
import { useCallback, useState } from "react";
import Toast from "react-native-toast-message";
export default function AdminInvestmentRejectInput() { export default function AdminInvestmentRejectInput() {
const { id } = useLocalSearchParams(); const { id, status } = useLocalSearchParams();
const [value, setValue] = useState(id as string); 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 = ( const buttonSubmit = (
<BoxButtonOnFooter> <BoxButtonOnFooter>
<AdminButtonReject <AdminButtonReject
isLoading={isLoading}
title="Reject" title="Reject"
onReject={() => onReject={() =>
AlertDefaultSystem({ AlertDefaultSystem({
@@ -22,12 +91,8 @@ export default function AdminInvestmentRejectInput() {
message: "Apakah anda yakin ingin menolak data ini?", message: "Apakah anda yakin ingin menolak data ini?",
textLeft: "Batal", textLeft: "Batal",
textRight: "Ya", textRight: "Ya",
onPressLeft: () => {
router.back();
},
onPressRight: () => { onPressRight: () => {
console.log("value:", value); handlerSubmit();
router.replace(`/admin/investment/reject/status`);
}, },
}) })
} }
@@ -39,7 +104,9 @@ export default function AdminInvestmentRejectInput() {
<> <>
<ViewWrapper <ViewWrapper
footerComponent={buttonSubmit} footerComponent={buttonSubmit}
headerComponent={<AdminBackButtonAntTitle title="Penolakan Investasi" />} headerComponent={
<AdminBackButtonAntTitle title="Penolakan Investasi" />
}
> >
<TextAreaCustom <TextAreaCustom
value={value} value={value}

View File

@@ -1,69 +1,114 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { import {
ActionIcon, ActionIcon,
SearchInput, LoaderCustom,
Spacing, SearchInput,
TextCustom, StackCustom,
ViewWrapper TextCustom,
ViewWrapper
} from "@/components"; } from "@/components";
import AdminComp_BoxTitle from "@/components/_ShareComponent/Admin/BoxTitlePage"; import AdminComp_BoxTitle from "@/components/_ShareComponent/Admin/BoxTitlePage";
import AdminTitleTable from "@/components/_ShareComponent/Admin/TableTitle"; import AdminTitleTable from "@/components/_ShareComponent/Admin/TableTitle";
import AdminTableValue from "@/components/_ShareComponent/Admin/TableValue"; 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 { ICON_SIZE_BUTTON } from "@/constants/constans-value";
import { apiAdminInvestment } from "@/service/api-admin/api-admin-investment";
import { Octicons } from "@expo/vector-icons"; import { Octicons } from "@expo/vector-icons";
import { router, useLocalSearchParams } from "expo-router"; import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
import _ from "lodash"; import _ from "lodash";
import React, { useCallback } from "react";
import { Divider } from "react-native-paper"; import { Divider } from "react-native-paper";
export default function AdminInvestmentStatus() { export default function AdminInvestmentStatus() {
const { status } = useLocalSearchParams(); 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 = ( const rightComponent = (
<SearchInput <SearchInput
containerStyle={{ width: "100%", marginBottom: 0 }} containerStyle={{ width: "100%", marginBottom: 0 }}
placeholder="Cari" placeholder="Cari"
value={search}
onChangeText={setSearch}
/> />
); );
return ( return (
<> <>
<ViewWrapper <ViewWrapper headerComponent={<AdminTitlePage title="Investasi" />}>
headerComponent={ <StackCustom gap={"sm"}>
<AdminComp_BoxTitle <AdminComp_BoxTitle
title={`Investasi ${_.startCase(status as string)}`} title={`${_.startCase(status as string)}`}
rightComponent={rightComponent} rightComponent={rightComponent}
/> />
} <AdminTitleTable
> title1="Aksi"
<AdminTitleTable title2="Username"
title1="Aksi" title3="Judul Investasi"
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>
}
/> />
))}
<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> </ViewWrapper>
</> </>
); );

View File

@@ -7,8 +7,51 @@ import {
import AdminComp_BoxDashboard from "@/components/_ShareComponent/Admin/BoxDashboard"; import AdminComp_BoxDashboard from "@/components/_ShareComponent/Admin/BoxDashboard";
import AdminTitlePage from "@/components/_ShareComponent/Admin/TitlePage"; import AdminTitlePage from "@/components/_ShareComponent/Admin/TitlePage";
import { MainColor } from "@/constants/color-palet"; 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() { 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 ( return (
<> <>
<ViewWrapper> <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

@@ -1,14 +1,14 @@
/* eslint-disable react-hooks/exhaustive-deps */ /* eslint-disable react-hooks/exhaustive-deps */
import { import {
AlertDefaultSystem, AlertDefaultSystem,
BadgeCustom, BadgeCustom,
BaseBox, BaseBox,
CircleContainer, CircleContainer,
Grid, Grid,
Spacing, Spacing,
StackCustom, StackCustom,
TextCustom, TextCustom,
ViewWrapper, ViewWrapper,
} from "@/components"; } from "@/components";
import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButtonAntTitle"; import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButtonAntTitle";
import AdminButtonReject from "@/components/_ShareComponent/Admin/ButtonReject"; import AdminButtonReject from "@/components/_ShareComponent/Admin/ButtonReject";
@@ -18,7 +18,7 @@ import ReportBox from "@/components/Box/ReportBox";
import { MainColor } from "@/constants/color-palet"; import { MainColor } from "@/constants/color-palet";
import funUpdateStatusVoting from "@/screens/Admin/Voting/funUpdateStatus"; import funUpdateStatusVoting from "@/screens/Admin/Voting/funUpdateStatus";
import { apiAdminVotingById } from "@/service/api-admin/api-admin-voting"; import { apiAdminVotingById } from "@/service/api-admin/api-admin-voting";
import { colorBadge } from "@/utils/colorBadge"; import { colorBadgeStatus } from "@/utils/colorBadge";
import { dateTimeView } from "@/utils/dateTimeView"; import { dateTimeView } from "@/utils/dateTimeView";
import { Entypo } from "@expo/vector-icons"; import { Entypo } from "@expo/vector-icons";
import dayjs from "dayjs"; import dayjs from "dayjs";
@@ -68,7 +68,7 @@ export default function AdminVotingDetail() {
label: "Status", label: "Status",
value: value:
data && data?.Voting_Status?.name ? ( data && data?.Voting_Status?.name ? (
<BadgeCustom color={colorBadge({ status: status as string })}> <BadgeCustom color={colorBadgeStatus({ status: status as string })}>
{status === "history" ? "Riwayat" : _.startCase(status as string)} {status === "history" ? "Riwayat" : _.startCase(status as string)}
</BadgeCustom> </BadgeCustom>
) : ( ) : (

View File

@@ -196,6 +196,7 @@ const DateTimeInput_Android: React.FC<DateTimeInputProps> = ({
onChange={handleConfirmTime} onChange={handleConfirmTime}
minimumDate={minimumDate} minimumDate={minimumDate}
maximumDate={maximumDate} maximumDate={maximumDate}
themeVariant="light"
/> />
)} )}
</> </>

View File

@@ -145,6 +145,7 @@ const DateTimeInput_IOS: React.FC<DateTimeInputProps> = ({
onChange={handleConfirm} onChange={handleConfirm}
minimumDate={minimumDate} minimumDate={minimumDate}
maximumDate={maximumDate} maximumDate={maximumDate}
themeVariant="light"
/> />
</View> </View>
</> </>

View File

@@ -27,6 +27,8 @@ type SelectProps = {
onChange: (value: string | number) => void; onChange: (value: string | number) => void;
borderRadius?: number; borderRadius?: number;
styleContainer?: StyleProp<ViewStyle>; styleContainer?: StyleProp<ViewStyle>;
allowClear?: boolean; // <-- new prop
clearLabel?: string; // default: "Kosongkan"
}; };
const SelectCustom: React.FC<SelectProps> = ({ const SelectCustom: React.FC<SelectProps> = ({
@@ -39,13 +41,21 @@ const SelectCustom: React.FC<SelectProps> = ({
onChange, onChange,
borderRadius = 8, borderRadius = 8,
styleContainer, styleContainer,
allowClear = true, // bisa dimatikan jika tidak perlu
clearLabel = "Hapus Pilihan",
}) => { }) => {
const [modalVisible, setModalVisible] = useState(false); const [modalVisible, setModalVisible] = useState(false);
const selectedItem = data.find((item) => item.value === value); const selectedItem = data.find((item) => item.value === value);
const hasError = required && value === null; // <-- check if empty and required const hasError = required && value === null; // <-- check if empty and required
// Gabungkan opsi clear di atas daftar
const renderData = allowClear
? [...data,
// { label: clearLabel, value: "__clear__" }
]
: data;
return ( return (
<View style={[GStyles.inputContainerArea, styleContainer]}> <View style={[GStyles.inputContainerArea, styleContainer]}>
{label && ( {label && (
@@ -54,29 +64,64 @@ const SelectCustom: React.FC<SelectProps> = ({
{required && <Text style={GStyles.inputRequired}> *</Text>} {required && <Text style={GStyles.inputRequired}> *</Text>}
</Text> </Text>
)} )}
<Pressable
{/* Input Container */}
<View
style={[ style={[
{ borderRadius, }, {
hasError ? GStyles.inputErrorBorder : null, flexDirection: "row",
alignItems: "center",
// backgroundColor: "red",
// flex: 1,
borderRadius,
},
GStyles.inputContainerInput, GStyles.inputContainerInput,
hasError ? GStyles.inputErrorBorder : null,
disabled && GStyles.disabledBox, disabled && GStyles.disabledBox,
]} // <-- add error style ]}
onPress={() => !disabled && setModalVisible(true)}
> >
<Text <Pressable
style={ style={[
selectedItem {
? disabled flex: 1,
? GStyles.inputTextDisabled borderRadius,
: GStyles.inputText flexDirection: "row",
: disabled alignItems: "center",
? GStyles.inputPlaceholderDisabled paddingHorizontal: 10,
: GStyles.inputPlaceholder height: 50,
} },
// GStyles.inputContainerInput,
// hasError ? GStyles.inputErrorBorder : null,
// disabled && GStyles.disabledBox,
]}
onPress={() => !disabled && setModalVisible(true)}
> >
{selectedItem?.label || placeholder} <Text
</Text> style={
</Pressable> selectedItem
? disabled
? GStyles.inputTextDisabled
: GStyles.inputText
: disabled
? GStyles.inputPlaceholderDisabled
: GStyles.inputPlaceholder
}
>
{selectedItem?.label || placeholder}
</Text>
</Pressable>
{/* Tombol Clear (Hanya muncul jika ada nilai terpilih & allowClear aktif) */}
{!disabled && allowClear && value !== null && value !== undefined && (
<TouchableOpacity
style={{ paddingHorizontal: 10 }}
onPress={() => onChange(null as any)} // null dikirim sebagai value
>
<Text style={{ fontSize: 18, color: "#999" }}>×</Text>
</TouchableOpacity>
)}
</View>
<Modal visible={modalVisible} transparent animationType="fade"> <Modal visible={modalVisible} transparent animationType="fade">
<TouchableOpacity <TouchableOpacity
@@ -86,19 +131,38 @@ const SelectCustom: React.FC<SelectProps> = ({
> >
<View style={GStyles.selectModalContent}> <View style={GStyles.selectModalContent}>
<FlatList <FlatList
data={data} data={renderData}
keyExtractor={(item) => String(item.value)} keyExtractor={(item) => String(item.value)}
renderItem={({ item }) => ( renderItem={({ item }) => {
<TouchableOpacity if (item.value === "__clear__") {
style={GStyles.selectOption} return (
onPress={() => { <TouchableOpacity
onChange(item.value); style={[
setModalVisible(false); GStyles.selectOption,
}} { backgroundColor: "#fdd" },
> ]}
<Text>{item.label}</Text> onPress={() => {
</TouchableOpacity> onChange(null as any); // kosongkan nilai
)} setModalVisible(false);
}}
>
<Text>{item.label}</Text>
</TouchableOpacity>
);
}
return (
<TouchableOpacity
style={GStyles.selectOption}
onPress={() => {
onChange(item.value);
setModalVisible(false);
}}
>
<Text>{item.label}</Text>
</TouchableOpacity>
);
}}
/> />
</View> </View>
</TouchableOpacity> </TouchableOpacity>

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>
);
}

View File

@@ -91,14 +91,11 @@ export const AuthProvider = ({ children }: { children: React.ReactNode }) => {
setToken(token); setToken(token);
await AsyncStorage.setItem("authToken", token); await AsyncStorage.setItem("authToken", token);
const responseUser = await apiConfig.get( const responseUser = await apiConfig.get(`/mobile?token=${token}`, {
`/mobile?token=${token}`, headers: {
{ Authorization: `Bearer ${token}`,
headers: { },
Authorization: `Bearer ${token}`, });
},
}
);
const dataUser = responseUser.data.data; const dataUser = responseUser.data.data;
setUser(dataUser); setUser(dataUser);
@@ -147,9 +144,7 @@ export const AuthProvider = ({ children }: { children: React.ReactNode }) => {
await AsyncStorage.setItem("userData", JSON.stringify(dataUser)); await AsyncStorage.setItem("userData", JSON.stringify(dataUser));
return dataUser; return dataUser;
} catch (error: any) { } catch (error: any) {
throw new Error( console.log(error.response?.data?.message + "user" || "Gagal mengambil data user");
error.response?.data?.message || "Gagal mengambil data user"
);
} finally { } finally {
setIsLoading(false); setIsLoading(false);
} }

View File

@@ -6,7 +6,11 @@ import {
DummyLandscapeImage, DummyLandscapeImage,
} from "@/components"; } from "@/components";
export default function AdminDonation_BoxOfDonationStory() { export default function AdminDonation_BoxOfDonationStory({
data,
}: {
data: any;
}) {
return ( return (
<> <>
<BaseBox> <BaseBox>
@@ -14,19 +18,9 @@ export default function AdminDonation_BoxOfDonationStory() {
<Spacing /> <Spacing />
<StackCustom> <StackCustom>
<TextCustom> <TextCustom>{(data && data?.pembukaan) || "-"}</TextCustom>
Lorem ipsum dolor sit, amet consectetur adipisicing elit. Rem magni <DummyLandscapeImage imageId={data?.imageId || "-"} />
perspiciatis eius ipsam provident, impedit, fugiat aliquid nobis <TextCustom>{(data && data?.cerita) || "-"}</TextCustom>
pariatur asperiores fuga quidem temporibus labore, molestias
perferendis optio ipsum. Praesentium, tempore?
</TextCustom>
<DummyLandscapeImage />
<TextCustom>
Lorem ipsum dolor sit, amet consectetur adipisicing elit. Rem magni
perspiciatis eius ipsam provident, impedit, fugiat aliquid nobis
pariatur asperiores fuga quidem temporibus labore, molestias
perferendis optio ipsum. Praesentium, tempore?
</TextCustom>
</StackCustom> </StackCustom>
</BaseBox> </BaseBox>
</> </>

View File

@@ -0,0 +1,23 @@
import { apiAdminDonationUpdateStatus } from "@/service/api-admin/api-admin-donation";
export const funUpdateStatusDonation = async ({
id,
changeStatus,
data,
}: {
id: string;
changeStatus: "publish" | "review" | "reject";
data?: string;
}) => {
try {
const response = await apiAdminDonationUpdateStatus({
id: id,
changeStatus: changeStatus as any,
data: data,
});
return response;
} catch (error) {
console.log("[ERROR]", error);
throw error;
}
};

View File

@@ -1,11 +1,14 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { import {
BaseBox, BaseBox,
Grid,
DummyLandscapeImage, DummyLandscapeImage,
Grid,
ProgressCustom,
StackCustom, StackCustom,
TextCustom, TextCustom,
ProgressCustom,
} from "@/components"; } from "@/components";
import { countDownAndCondition } from "@/utils/countDownAndCondition";
import { useEffect, useState } from "react";
import { View } from "react-native"; import { View } from "react-native";
export default function Donation_BoxPublish({ export default function Donation_BoxPublish({
@@ -15,6 +18,27 @@ export default function Donation_BoxPublish({
id: string; id: string;
data: any; data: any;
}) { }) {
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 ( return (
<> <>
<BaseBox paddingTop={7} paddingBottom={7} href={`/donation/${id}`}> <BaseBox paddingTop={7} paddingBottom={7} href={`/donation/${id}`}>
@@ -36,13 +60,22 @@ export default function Donation_BoxPublish({
{data?.title || "-"} {data?.title || "-"}
</TextCustom> </TextCustom>
<TextCustom size="small"> <TextCustom size="small">
Sisa hari: {data?.durasiDonasi || 0} {value.reminder ? (
<TextCustom bold color="red">
Waktu berakhir
</TextCustom>
) : (
<TextCustom>Sisa hari: {value.sisa}</TextCustom>
)}
</TextCustom> </TextCustom>
</View> </View>
<ProgressCustom <ProgressCustom
label={data?.progres + "%" || "0%"}
value={data?.progres || 0}
size="lg" size="lg"
value={Number(data?.progres) || 0}
showLabel={true}
label={data?.progres + "%"}
animated
color="primary"
/> />
{/* <TextCustom> {/* <TextCustom>
Terkumpul : Rp 300.000 Terkumpul : Rp 300.000

View File

@@ -1,9 +1,9 @@
import { import {
BaseBox, BaseBox,
StackCustom,
DummyLandscapeImage, DummyLandscapeImage,
TextCustom,
Grid, Grid,
StackCustom,
TextCustom,
} from "@/components"; } from "@/components";
import { formatCurrencyDisplay } from "@/utils/formatCurrencyDisplay"; import { formatCurrencyDisplay } from "@/utils/formatCurrencyDisplay";
import React from "react"; import React from "react";
@@ -12,10 +12,15 @@ import { View } from "react-native";
export default function Donation_ComponentBoxDetailData({ export default function Donation_ComponentBoxDetailData({
bottomSection, bottomSection,
data, data,
sisaHari,
reminder,
}: { }: {
bottomSection?: React.ReactNode; bottomSection?: React.ReactNode;
data: any; data: any;
sisaHari: number;
reminder: boolean;
}) { }) {
return ( return (
<> <>
<BaseBox> <BaseBox>
@@ -25,9 +30,13 @@ export default function Donation_ComponentBoxDetailData({
<TextCustom bold size="large"> <TextCustom bold size="large">
{data?.title || "-"} {data?.title || "-"}
</TextCustom> </TextCustom>
<TextCustom size="small"> {reminder ? (
Durasi: {data?.DonasiMaster_Durasi?.name || "-"} <TextCustom bold color="red">
</TextCustom> Waktu berakhir
</TextCustom>
) : (
<TextCustom>Sisa hari: {sisaHari}</TextCustom>
)}
</View> </View>
<Grid> <Grid>

View File

@@ -11,11 +11,23 @@ import { Ionicons, MaterialIcons } from "@expo/vector-icons";
import { router } from "expo-router"; import { router } from "expo-router";
import { View } from "react-native"; import { View } from "react-native";
export default function Donation_ProgressSection({ id }: { id: string }) { export default function Donation_ProgressSection({
id,
progres,
}: {
id: string;
progres: number;
}) {
return ( return (
<> <>
<View> <View>
<ProgressCustom size="lg" /> <ProgressCustom
size="lg"
value={progres}
label={progres + "%"}
animated
color="primary"
/>
<Spacing /> <Spacing />
<Grid> <Grid>
<Grid.Col span={4}> <Grid.Col span={4}>

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> <BaseBox>
<StackCustom> <StackCustom>
<TextCustom bold>Progress Saham</TextCustom> <TextCustom bold>Progress Saham</TextCustom>
<ProgressCustom label={progress + "%"} value={progress} size="lg" /> <ProgressCustom label={(progress || 0) + "%"} value={progress || 0} size="lg" />
</StackCustom> </StackCustom>
</BaseBox> </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 { import {
listDataNotPublishInvesment, listDataNotPublishInvesment,
listDataPublishInvesment, listDataPublishInvesment,
} from "@/lib/dummy-data/investment/dummy-data-not-publish"; } 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_BoxDetailDataSection from "./BoxDetailDataSection";
import Invesment_BoxProgressSection from "./BoxProgressSection"; import Invesment_BoxProgressSection from "./BoxProgressSection";
import Investment_ButtonStatusSection from "./ButtonStatusSection"; import Investment_ButtonStatusSection from "./ButtonStatusSection";
@@ -19,11 +22,39 @@ export default function Invesment_DetailDataPublishSection({
bottomSection?: React.ReactNode; bottomSection?: React.ReactNode;
buttonSection?: 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 ( return (
<> <>
<StackCustom gap={"sm"}> <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 <Invesment_BoxDetailDataSection
title={data?.title} title={data?.title}
author={data?.author} author={data?.author}
@@ -35,11 +66,18 @@ export default function Invesment_DetailDataPublishSection({
} }
bottomSection={bottomSection} bottomSection={bottomSection}
/> />
<Investment_ButtonStatusSection
id={data?.id} {value.reminder ? (
status={status as string} <ButtonCustom disabled>
buttonPublish={buttonSection} Periode Investasi Berakhir
/> </ButtonCustom>
) : (
<Investment_ButtonStatusSection
id={data?.id}
status={status as string}
buttonPublish={buttonSection}
/>
)}
</StackCustom> </StackCustom>
<Spacing /> <Spacing />
</> </>

View File

@@ -0,0 +1,154 @@
import { apiConfig } from "../api-config";
export async function apiAdminDonation({
category,
search,
}: {
category: "dashboard" | "publish" | "review" | "reject";
search?: string;
}) {
try {
const response = await apiConfig.get(
`/mobile/admin/donation?category=${category}&search=${search}`
);
return response.data;
} catch (error) {
throw error;
}
}
export async function apiAdminDonationDetailById({ id }: { id: string }) {
try {
const response = await apiConfig.get(`/mobile/admin/donation/${id}`);
return response.data;
} catch (error) {
throw error;
}
}
export async function apiAdminDonationUpdateStatus({
id,
changeStatus,
data,
}: {
id: string;
changeStatus: "publish" | "review" | "reject";
data?: string;
}) {
try {
const response = await apiConfig.put(
`/mobile/admin/donation/${id}?status=${changeStatus}`,
{
data: data,
}
);
return response.data;
} catch (error) {
throw error;
}
}
export async function apiAdminDonationListOfDonatur({
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/donation/${id}/donatur${query}`
);
return response.data;
} catch (error) {
throw error;
}
}
export async function apiAdminDonationInvoiceDetailById({
id,
}: {
id: string;
}) {
try {
const response = await apiConfig.get(
`/mobile/admin/donation/${id}/invoice`
);
return response.data;
} catch (error) {
throw error;
}
}
export async function apiAdminDonationInvoiceUpdateById({
id,
data,
status,
}: {
id: string;
data: any;
status: "berhasil" | "gagal" | "proses" | "menunggu";
}) {
try {
const response = await apiConfig.put(
`/mobile/admin/donation/${id}/invoice?status=${status}`,
{
data: data,
}
);
return response.data;
} catch (error) {
throw error;
}
}
export async function apiAdminDonationListOfDonaturById({
id,
}: {
id: string;
}) {
try {
const response = await apiConfig.get(`/mobile/donation/${id}/donatur`);
return response.data;
} catch (error) {
throw error;
}
}
export async function apiAdminDonationDisbursementOfFundsCreated({
id,
data,
}: {
id: string;
data: any;
}) {
try {
const response = await apiConfig.post(
`/mobile/admin/donation/${id}/disbursement`,
{
data: data,
}
);
return response.data;
} catch (error) {
throw error;
}
}
export async function apiAdminDonationDisbursementOfFundsListById({
id,
category,
}: {
id: string;
category: "get-all" | "get-one"
}) {
try {
const response = await apiConfig.get(`/mobile/admin/donation/${id}/disbursement?category=${category}`);
return response.data;
} catch (error) {
throw error;
}
}

View File

@@ -48,3 +48,15 @@ export async function apiAdminEventUpdateStatus({
} }
} }
export async function apiAdminEventListOfParticipants({ id }: { id: string }) {
try {
const response = await apiConfig.get(
`/mobile/admin/event/${id}/participants`
);
return response.data;
} catch (error) {
throw error;
}
}

View File

@@ -0,0 +1,88 @@
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;
}
}

View File

@@ -62,7 +62,9 @@ export async function apiAdminMasterBusinessField() {
export async function apiAdminMasterBusinessFieldById({ id }: { id: string }) { export async function apiAdminMasterBusinessFieldById({ id }: { id: string }) {
try { try {
const response = await apiConfig.get(`/mobile/admin/master/business-field/${id}`); const response = await apiConfig.get(
`/mobile/admin/master/business-field/${id}`
);
return response.data; return response.data;
} catch (error) { } catch (error) {
throw error; throw error;
@@ -77,26 +79,91 @@ export async function apiAdminMasterBusinessFieldUpdate({
data: any; data: any;
}) { }) {
try { try {
const response = await apiConfig.put(`/mobile/admin/master/business-field/${id}`, { const response = await apiConfig.put(
data: data, `/mobile/admin/master/business-field/${id}`,
}); {
data: data,
}
);
return response.data; return response.data;
} catch (error) { } catch (error) {
throw error; throw error;
} }
} }
export async function apiAdminMasterBusinessFieldCreate({ data }: { data: any }) { export async function apiAdminMasterBusinessFieldCreate({
data,
}: {
data: any;
}) {
try { try {
const response = await apiConfig.post(`/mobile/admin/master/business-field`, { const response = await apiConfig.post(
data: data, `/mobile/admin/master/business-field`,
}); {
data: data,
}
);
return response.data; return response.data;
} catch (error) { } catch (error) {
throw error; throw error;
} }
} }
// ================== END BUSINNES FIELD ================== // // ================== END BUSINNES FIELD ================== //
// ================== START EVENT ================== //
export async function apiAdminMasterTypeOfEvent() {
try {
const response = await apiConfig.get(`/mobile/admin/master/type-of-event`);
return response.data;
} catch (error) {
throw error;
}
}
export async function apiEventCreateTypeOfEvent({ data }: { data: string }) {
try {
const response = await apiConfig.post(
`/mobile/admin/master/type-of-event`,
{
data: data,
}
);
return response.data;
} catch (error) {
throw error;
}
}
export async function apiAdminMasterTypeOfEventGetOne({ id }: { id: string }) {
try {
const response = await apiConfig.get(
`/mobile/admin/master/type-of-event/${id}`
);
return response.data;
} catch (error) {
throw error;
}
}
export async function apiAdminMasterTypeOfEventUpdate({
id,
data,
}: {
id: string;
data: any;
}) {
try {
const response = await apiConfig.put(
`/mobile/admin/master/type-of-event/${id}`,
{
data: data,
}
);
return response.data;
} catch (error) {
throw error;
}
}
// ================== END EVENT ================== //

View File

@@ -255,3 +255,16 @@ export async function apiDonationDeleteNews({ id }: { id: string }) {
throw error; throw error;
} }
} }
export async function apiDonationDisbursementOfFundsListById({
id,
}: {
id: string;
}) {
try {
const response = await apiConfig.get(`/mobile/donation/${id}/disbursement`);
return response.data;
} catch (error) {
throw error;
}
}

View File

@@ -177,3 +177,5 @@ export async function apiEventConfirmationAction({
throw error; throw error;
} }
} }

View File

@@ -157,3 +157,14 @@ export async function apiMasterDonation({
throw error; throw error;
} }
} }
// ================== END MASTER DONATION ================== //
export async function apiMasterTransaction() {
try {
const response = await apiConfig.get(`/mobile/master/transaction-status`);
return response.data;
} catch (error) {
throw error;
}
}

View File

@@ -0,0 +1,9 @@
import { AccentColor } from "@/constants/color-palet";
export const colorActivationForBadge = ({ status }: { status: boolean }) => {
if (status) {
return AccentColor.blue;
} else {
return AccentColor.blackgray;
}
};

View File

@@ -1,6 +1,6 @@
import { MainColor } from "@/constants/color-palet"; import { AccentColor, MainColor } from "@/constants/color-palet";
export const colorBadge = ({ status }: { status: string }) => { export const colorBadgeStatus = ({ status }: { status: string }) => {
const statusLowerCase = status.toLowerCase(); const statusLowerCase = status.toLowerCase();
if (statusLowerCase === "publish") { if (statusLowerCase === "publish") {
return MainColor.green; return MainColor.green;
@@ -12,3 +12,16 @@ export const colorBadge = ({ status }: { status: string }) => {
return MainColor.placeholder; return MainColor.placeholder;
} }
}; };
export const colorBadgeTransaction = ({ status }: { status: string }) => {
const statusLowerCase = status.toLowerCase();
if (statusLowerCase === "berhasil") {
return MainColor.green;
} else if (statusLowerCase === "menunggu") {
return MainColor.orange;
} else if (statusLowerCase === "gagal") {
return MainColor.red;
} else {
return AccentColor.blue;
}
};

View File

@@ -0,0 +1,21 @@
import dayjs from "dayjs";
export function countDownAndCondition({
publishTime,
duration,
}: {
publishTime: Date;
duration: number | string;
}) {
const now = dayjs();
const publish = dayjs(publishTime);
const diffTime = publish.diff(now, "day");
const durasi = Number(duration);
const sisaHari = durasi + diffTime;
return {
durationDay: sisaHari,
reminder: sisaHari <= 0,
};
}

View File

@@ -13,19 +13,28 @@ export interface IFileData {
export type AllowedFileType = "image" | "pdf" | undefined; export type AllowedFileType = "image" | "pdf" | undefined;
export type AspectRatio = [number, number];
export interface PickFileOptions { export interface PickFileOptions {
setImageUri?: (file: IFileData) => void; setImageUri?: (file: IFileData) => void;
setPdfUri?: (file: IFileData) => void; setPdfUri?: (file: IFileData) => void;
allowedType?: AllowedFileType; // <-- Tambahkan prop ini allowedType?: AllowedFileType; // <-- Tambahkan prop ini
aspectRatio?: AspectRatio;
} }
export default async function pickFile({ export default async function pickFile({
setImageUri, setImageUri,
setPdfUri, setPdfUri,
allowedType, allowedType,
aspectRatio,
}: PickFileOptions): Promise<void> { }: PickFileOptions): Promise<void> {
if (allowedType === "image") { if (allowedType === "image") {
await pickImage(setImageUri); if (aspectRatio) {
await pickImage(setImageUri, aspectRatio);
} else {
// Jika tidak, tawarkan pilihan rasio (default [4,3])
showAspectRatioChoice(setImageUri);
}
} else if (allowedType === "pdf") { } else if (allowedType === "pdf") {
await pickPdf(setPdfUri); await pickPdf(setPdfUri);
} else { } else {
@@ -36,15 +45,45 @@ export default async function pickFile({
[ [
{ text: "Batal", style: "cancel" }, { text: "Batal", style: "cancel" },
{ text: "Dokumen (PDF)", onPress: () => pickPdf(setPdfUri) }, { text: "Dokumen (PDF)", onPress: () => pickPdf(setPdfUri) },
{ text: "Gambar", onPress: () => pickImage(setImageUri) }, { text: "Gambar", onPress: () => pickImage(setImageUri, aspectRatio) },
], ],
{ cancelable: true } { cancelable: true }
); );
} }
} }
function showAspectRatioChoice(setImageUri?: (file: IFileData) => void) {
Alert.alert(
"Pilih Rasio Gambar",
"Pilih rasio crop yang diinginkan:",
[
{ text: "Batal", style: "cancel" },
{
text: "1:1 (Kotak)",
onPress: () => pickImage(setImageUri, [1, 1]),
},
// {
// text: "4:3 (Default)",
// onPress: () => pickImage(setImageUri, [4, 3]),
// },
// {
// text: "9:16 (Landscape)",
// onPress: () => pickImage(setImageUri, [9, 16]),
// },
{
text: "3:4 (Potret)",
onPress: () => pickImage(setImageUri, [3, 4]),
},
],
{ cancelable: true }
);
}
// --- Fungsi internal: pickImage --- // --- Fungsi internal: pickImage ---
async function pickImage(setImageUri?: (file: IFileData) => void) { async function pickImage(
setImageUri?: (file: IFileData) => void,
aspectRatio: AspectRatio = [4, 3] // Default [4, 3]
) {
const { status } = await ImagePicker.requestMediaLibraryPermissionsAsync(); const { status } = await ImagePicker.requestMediaLibraryPermissionsAsync();
if (status !== "granted") { if (status !== "granted") {
Alert.alert( Alert.alert(
@@ -57,7 +96,7 @@ async function pickImage(setImageUri?: (file: IFileData) => void) {
const result = await ImagePicker.launchImageLibraryAsync({ const result = await ImagePicker.launchImageLibraryAsync({
mediaTypes: ImagePicker.MediaTypeOptions.Images, mediaTypes: ImagePicker.MediaTypeOptions.Images,
allowsEditing: true, allowsEditing: true,
aspect: [4, 3], aspect: aspectRatio, // 🎯 Gunakan rasio dinamis
quality: 1, quality: 1,
}); });