Fix: tampilan status dan detail status sudah terintegrasi API
- create dan buntton status sudah terintegrasi

### No Issue
This commit is contained in:
2025-10-06 16:00:20 +08:00
parent f3a3acc747
commit ba878d4d08
11 changed files with 395 additions and 96 deletions

View File

@@ -186,7 +186,7 @@ export default function UserLayout() {
name="crowdfunding/index"
options={{
title: "Crowdfunding",
headerLeft: () => <BackButton />,
headerLeft: () => <BackButton path="/home" />,
}}
/>

View File

@@ -1,12 +1,48 @@
import { ScrollableCustom, ViewWrapper } from "@/components";
/* eslint-disable react-hooks/exhaustive-deps */
import {
LoaderCustom,
ScrollableCustom,
TextCustom,
ViewWrapper,
} from "@/components";
import { useAuth } from "@/hooks/use-auth";
import { dummyMasterStatus } from "@/lib/dummy-data/_master/status";
import Donasi_BoxStatus from "@/screens/Donation/BoxStatus";
import { useState } from "react";
import { apiDonationGetByStatus } from "@/service/api-client/api-donation";
import { useFocusEffect } from "expo-router";
import _ from "lodash";
import { useCallback, useState } from "react";
export default function DonationStatus() {
const { user } = useAuth();
const [activeCategory, setActiveCategory] = useState<string | null>(
"publish"
);
const [listData, setListData] = useState<any[] | null>(null);
const [loadList, setLoadList] = useState(false);
useFocusEffect(
useCallback(() => {
onLoadList();
}, [activeCategory])
);
const onLoadList = async () => {
try {
setLoadList(true);
const response = await apiDonationGetByStatus({
authorId: user?.id as string,
status: activeCategory as string,
});
setListData(response.data);
} catch (error) {
console.log("[ERROR]", error);
setListData(null);
} finally {
setLoadList(false);
}
};
const handlePress = (item: any) => {
setActiveCategory(item.value);
@@ -26,13 +62,19 @@ export default function DonationStatus() {
);
return (
<ViewWrapper hideFooter headerComponent={scrollComponent}>
{Array.from({ length: 10 }).map((_, index) => (
<Donasi_BoxStatus
key={index}
id={index.toString()}
status={activeCategory as string}
/>
))}
{loadList ? (
<LoaderCustom />
) : _.isEmpty(listData) ? (
<TextCustom align="center">Tidak ada data {activeCategory}</TextCustom>
) : (
listData?.map((item: any, index: number) => (
<Donasi_BoxStatus
key={index}
data={item}
status={activeCategory as string}
/>
))
)}
</ViewWrapper>
);
}

View File

@@ -1,3 +1,4 @@
/* eslint-disable react-hooks/exhaustive-deps */
import {
BackButton,
DotButton,
@@ -14,16 +15,43 @@ import Donation_ButtonStatusSection from "@/screens/Donation/ButtonStatusSection
import Donation_ComponentBoxDetailData from "@/screens/Donation/ComponentBoxDetailData";
import Donation_ComponentStoryFunrising from "@/screens/Donation/ComponentStoryFunrising";
import Donation_ProgressSection from "@/screens/Donation/ProgressSection";
import { apiDonationGetOne } from "@/service/api-client/api-donation";
import { FontAwesome6 } from "@expo/vector-icons";
import { router, Stack, useLocalSearchParams } from "expo-router";
import {
router,
Stack,
useFocusEffect,
useLocalSearchParams,
} from "expo-router";
import _ from "lodash";
import { useState } from "react";
import { useCallback, useState } from "react";
export default function DonasiDetailStatus() {
const { id, status } = useLocalSearchParams();
const [openDrawer, setOpenDrawer] = useState(false);
const [openDrawerPublish, setOpenDrawerPublish] = useState(false);
const [data, setData] = useState<any>();
useFocusEffect(
useCallback(() => {
onLoadData();
}, [id])
);
const onLoadData = async () => {
try {
const response = await apiDonationGetOne({
id: id as string,
category: "permanent",
});
setData(response.data);
} catch (error) {
console.log("[ERROR]", error);
}
};
const handlePress = (item: IMenuDrawerItem) => {
console.log("PATH ", item.path);
router.navigate(item.path as any);
@@ -46,13 +74,22 @@ export default function DonasiDetailStatus() {
/>
<ViewWrapper>
<Donation_ComponentBoxDetailData
data={data}
bottomSection={
status === "publish" && <Donation_ProgressSection id={id as string} />
status === "publish" && (
<Donation_ProgressSection id={id as string} />
)
}
/>
<Donation_ComponentStoryFunrising id={id as string} />
<Donation_ComponentStoryFunrising
id={id as string}
dataStory={data?.CeritaDonasi}
/>
<Spacing />
<Donation_ButtonStatusSection status={status as string} />
<Donation_ButtonStatusSection
id={id as string}
status={status as string}
/>
<Spacing />
</ViewWrapper>

View File

@@ -1,27 +1,42 @@
/* eslint-disable react-hooks/exhaustive-deps */
import {
DummyLandscapeImage,
StackCustom,
TextCustom,
ViewWrapper,
} from "@/components";
import { useLocalSearchParams } from "expo-router";
import { apiDonationGetOne } from "@/service/api-client/api-donation";
import { useFocusEffect, useLocalSearchParams } from "expo-router";
import { useCallback, useState } from "react";
export default function DonationDetailStory() {
const { id } = useLocalSearchParams();
const [data, setData] = useState<any>();
useFocusEffect(
useCallback(() => {
onLoadData();
}, [id])
);
const onLoadData = async () => {
try {
const response = await apiDonationGetOne({
id: id as string,
category: "permanent",
});
setData(response.data.CeritaDonasi);
} catch (error) {
console.log("[ERROR]", error);
}
};
return (
<ViewWrapper>
<StackCustom>
<TextCustom>
Lorem {id} ipsum dolor, sit amet consectetur adipisicing elit. Fuga
quasi nam nesciunt nisi corporis alias modi, pariatur sit totam rem
fugiat ex similique magni, aliquam maiores officiis iure at adipisci.
</TextCustom>
<DummyLandscapeImage />
<TextCustom>
Lorem {id} ipsum dolor, sit amet consectetur adipisicing elit. Fuga
quasi nam nesciunt nisi corporis alias modi, pariatur sit totam rem
fugiat ex similique magni, aliquam maiores officiis iure at adipisci.
</TextCustom>
<TextCustom>{data?.pembukaan || "-"}</TextCustom>
<DummyLandscapeImage imageId={data?.imageId} />
<TextCustom>{data?.cerita || "-"}</TextCustom>
</StackCustom>
</ViewWrapper>
);

View File

@@ -12,8 +12,12 @@ import {
} from "@/components";
import DIRECTORY_ID from "@/constants/directory-id";
import { useAuth } from "@/hooks/use-auth";
import { apiDonationGetOne } from "@/service/api-client/api-donation";
import {
apiDonationCreate,
apiDonationGetOne,
} from "@/service/api-client/api-donation";
import { uploadFileService } from "@/service/upload-service";
import pickFile from "@/utils/pickFile";
import { router, useLocalSearchParams } from "expo-router";
import _ from "lodash";
import { useEffect, useState } from "react";
@@ -22,7 +26,6 @@ import Toast from "react-native-toast-message";
export default function DonationCreateStory() {
const { user } = useAuth();
const { id } = useLocalSearchParams();
console.log("[ID]", id);
const [temporary, setTemporary] = useState<any>();
const [data, setData] = useState({
pembukaan: "",
@@ -30,7 +33,8 @@ export default function DonationCreateStory() {
namaBank: "",
rekening: "",
});
const [imageDonasi, setImageDonasi] = useState<string | null>(null);
const [imageStory, setImageStory] = useState<string | null>(null);
const [isLoading, setLoading] = useState(false);
useEffect(() => {
onLoadData();
@@ -38,11 +42,12 @@ export default function DonationCreateStory() {
const onLoadData = async () => {
try {
// const response = await apiDonationGetOne({
// id: id as string,
// category: "temporary",
// });
// console.log("[RES GET ONE]", JSON.stringify(response, null, 2));
const response = await apiDonationGetOne({
id: id as string,
category: "temporary",
});
setTemporary(response.data);
} catch (error) {
console.log("[ERROR]", error);
}
@@ -58,28 +63,51 @@ export default function DonationCreateStory() {
}
try {
setLoading(true);
const responseUploadImageDonasi = await uploadFileService({
imageUri: imageDonasi,
imageUri: imageStory,
dirId: DIRECTORY_ID.donasi_cerita_image,
});
const newData = {
id: temporary?.id,
// Data Donasi
temporaryId: temporary?.id,
authorId: user?.id,
title: temporary?.title,
target: temporary?.target,
donasiMaster_KategoriId: temporary?.donasiMaster_KategoriId,
donasiMaster_DurasiId: temporary?.donasiMaster_DurasiId,
authorId: user?.id,
imageId: temporary?.imageId,
// Data Bank
namaBank: data.namaBank,
rekening: data.rekening,
imageId: temporary?.imageId,
CeritaDonasi: {
pembukaan: data.pembukaan,
cerita: data.cerita,
},
// Data Cerita
imageCeritaId: responseUploadImageDonasi.data.id,
pembukaan: data.pembukaan,
cerita: data.cerita,
};
const response = await apiDonationCreate({
data: newData,
category: "permanent",
});
if (!response.success) {
Toast.show({
type: "error",
text1: "Gagal membuat donasi",
});
}
Toast.show({
type: "success",
text1: "Donasi berhasil disimpan",
});
router.replace("/donation/status");
} catch (error) {
console.log("[ERROR]", error);
} finally {
setLoading(false);
}
};
@@ -106,10 +134,15 @@ export default function DonationCreateStory() {
onChangeText={(value) => setData({ ...data, cerita: value })}
/>
<LandscapeFrameUploaded />
<LandscapeFrameUploaded image={imageStory || ""} />
<ButtonCenteredOnly
onPress={() => {
router.push("/(application)/(image)/take-picture/123");
pickFile({
allowedType: "image",
setImageUri: ({ uri }) => {
setImageStory(uri);
},
});
}}
icon="upload"
>
@@ -122,17 +155,23 @@ export default function DonationCreateStory() {
label="Nama Bank"
placeholder="Masukkan nama bank"
required
value={data.namaBank}
onChangeText={(value) => setData({ ...data, namaBank: value })}
/>
<TextInputCustom
label="Nomor Rekening"
placeholder="Masukkan nomor rekening"
required
keyboardType="numeric"
value={data.rekening}
onChangeText={(value) => setData({ ...data, rekening: value })}
/>
<Spacing />
<ButtonCustom
isLoading={isLoading}
onPress={() => {
router.replace(`/donation/(tabs)/status`);
handlerSubmit();
}}
>
Simpan

View File

@@ -11,8 +11,6 @@ import {
ViewWrapper,
} from "@/components";
import DIRECTORY_ID from "@/constants/directory-id";
import { dummyDonasiDurasi } from "@/lib/dummy-data/donasi/durasi";
import { dummyDonasiKategori } from "@/lib/dummy-data/donasi/kategori";
import { apiDonationCreate } from "@/service/api-client/api-donation";
import { apiMasterDonation } from "@/service/api-client/api-master";
import { uploadFileService } from "@/service/upload-service";
@@ -110,8 +108,6 @@ export default function DonationCreate() {
category: "temporary",
});
console.log("[RESPONSE]", JSON.stringify(response, null, 2));
if (!response.success) {
Toast.show({
type: "error",
@@ -210,8 +206,8 @@ export default function DonationCreate() {
<ButtonCustom
isLoading={isLoading}
onPress={() => {
// handlerSubmit();
router.push(`/donation/create-story?id=${"dasdsadsa"}`);
handlerSubmit();
// router.push(`/donation/create-story?id=${"dasdsadsa"}`);
}}
>
Selanjutnya

View File

@@ -5,13 +5,14 @@ import {
StackCustom,
TextCustom,
} from "@/components";
import { formatCurrencyDisplay } from "@/utils/formatCurrencyDisplay";
import { View } from "react-native";
export default function Donasi_BoxStatus({
id,
data,
status,
}: {
id: string;
data: any;
status: string;
}) {
return (
@@ -19,26 +20,30 @@ export default function Donasi_BoxStatus({
<BaseBox
paddingTop={7}
paddingBottom={7}
href={`/donation/${id}/${status}/detail`}
href={`/donation/${data.id}/${status}/detail`}
>
<Grid>
<Grid.Col span={5}>
<DummyLandscapeImage unClickPath height={100} />
<DummyLandscapeImage
unClickPath
height={100}
imageId={data.imageId}
/>
</Grid.Col>
<Grid.Col span={1}>
<View />
</Grid.Col>
<Grid.Col span={6}>
<StackCustom>
<TextCustom truncate>
Judul Donasi: {status} Lorem ipsum dolor sit amet consectetur
adipisicing elit.
</TextCustom>
<TextCustom truncate={2} bold>{data.title || "-"}</TextCustom>
<View>
<TextCustom>Target Dana</TextCustom>
<TextCustom bold color="yellow">
Rp. 7.500.000
Rp.
{data && data?.target
? formatCurrencyDisplay(data.target)
: "-"}
</TextCustom>
</View>
</StackCustom>

View File

@@ -1,20 +1,54 @@
import { AlertDefaultSystem, ButtonCustom, Grid } from "@/components";
import {
apiDonationDelete,
apiDonationUpdateStatus,
} from "@/service/api-client/api-donation";
import { router } from "expo-router";
import { useState } from "react";
import Toast from "react-native-toast-message";
export default function Donation_ButtonStatusSection({
id,
status,
}: {
id: string;
status: string;
}) {
const handleBatalkanReview = () => {
const [isLoading, setLoading] = useState(false);
const [isLoadingDelete, setLoadingDelete] = useState(false);
const handleBatalkanReview = async () => {
AlertDefaultSystem({
title: "Batalkan Review",
message: "Apakah Anda yakin ingin batalkan review ini?",
textLeft: "Batal",
textRight: "Ya",
onPressRight: () => {
console.log("Hapus");
router.back();
onPressRight: async () => {
try {
setLoading(true);
const response = await apiDonationUpdateStatus({
id: id,
status: "draft",
});
if (!response.success) {
Toast.show({
type: "info",
text1: response.message,
});
return;
}
Toast.show({
type: "success",
text1: response.message,
});
router.back();
} catch (error) {
console.log("[ERROR]", error);
} finally {
setLoading(false);
}
},
});
};
@@ -25,9 +59,33 @@ export default function Donation_ButtonStatusSection({
message: "Apakah Anda yakin ingin ajukan review ini?",
textLeft: "Batal",
textRight: "Ya",
onPressRight: () => {
console.log("Hapus");
router.back();
onPressRight: async () => {
try {
setLoading(true);
const response = await apiDonationUpdateStatus({
id: id,
status: "review",
});
if (!response.success) {
Toast.show({
type: "info",
text1: response.message,
});
return;
}
Toast.show({
type: "success",
text1: response.message,
});
router.back();
} catch (error) {
console.log("[ERROR]", error);
} finally {
setLoading(false);
}
},
});
};
@@ -38,9 +96,33 @@ export default function Donation_ButtonStatusSection({
message: "Apakah Anda yakin ingin edit kembali ini?",
textLeft: "Batal",
textRight: "Ya",
onPressRight: () => {
console.log("Hapus");
router.back();
onPressRight: async () => {
try {
setLoading(true);
const response = await apiDonationUpdateStatus({
id: id,
status: "draft",
});
if (!response.success) {
Toast.show({
type: "info",
text1: response.message,
});
return;
}
Toast.show({
type: "success",
text1: response.message,
});
router.back();
} catch (error) {
console.log("[ERROR]", error);
} finally {
setLoading(false);
}
},
});
};
@@ -51,9 +133,30 @@ export default function Donation_ButtonStatusSection({
message: "Apakah Anda yakin ingin menghapus data ini?",
textLeft: "Batal",
textRight: "Hapus",
onPressRight: () => {
console.log("Hapus");
router.back();
onPressRight: async () => {
try {
setLoadingDelete(true);
const response = await apiDonationDelete({ id: id });
if (!response.success) {
Toast.show({
type: "info",
text1: response.message,
});
return;
}
Toast.show({
type: "success",
text1: response.message,
});
router.back();
} catch (error) {
console.log("[ERROR]", error);
} finally {
setLoadingDelete(false);
}
},
});
};
@@ -62,6 +165,7 @@ export default function Donation_ButtonStatusSection({
return (
<>
<ButtonCustom
isLoading={isLoadingDelete}
backgroundColor="red"
textColor="white"
onPress={handleOpenDeleteAlert}
@@ -78,7 +182,7 @@ export default function Donation_ButtonStatusSection({
case "review":
return (
<ButtonCustom onPress={handleBatalkanReview}>
<ButtonCustom isLoading={isLoading} onPress={handleBatalkanReview}>
Batalkan Review
</ButtonCustom>
);
@@ -88,7 +192,7 @@ export default function Donation_ButtonStatusSection({
<>
<Grid>
<Grid.Col span={6} style={{ paddingRight: 10 }}>
<ButtonCustom onPress={handleAjukanReview}>
<ButtonCustom isLoading={isLoading} onPress={handleAjukanReview}>
Ajukan Review
</ButtonCustom>
</Grid.Col>
@@ -104,7 +208,7 @@ export default function Donation_ButtonStatusSection({
<>
<Grid>
<Grid.Col span={6} style={{ paddingRight: 10 }}>
<ButtonCustom onPress={handleEditKembali}>
<ButtonCustom isLoading={isLoading} onPress={handleEditKembali}>
Edit Kembali
</ButtonCustom>
</Grid.Col>

View File

@@ -5,25 +5,29 @@ import {
TextCustom,
Grid,
} from "@/components";
import { formatCurrencyDisplay } from "@/utils/formatCurrencyDisplay";
import React from "react";
import { View } from "react-native";
export default function Donation_ComponentBoxDetailData({
bottomSection,
data,
}: {
bottomSection?: React.ReactNode;
data: any;
}) {
return (
<>
<BaseBox>
<StackCustom>
<DummyLandscapeImage />
<DummyLandscapeImage imageId={data?.imageId} />
<View>
<TextCustom bold size="large">
Judul Donasi: Lorem, ipsum dolor sit amet consectetur adipisicing
elit.
{data?.title || "-"}
</TextCustom>
<TextCustom size="small">
Durasi: {data?.DonasiMaster_Durasi?.name || "-"}
</TextCustom>
<TextCustom size="small">Durasi: 30 hari</TextCustom>
</View>
<Grid>
@@ -31,7 +35,7 @@ export default function Donation_ComponentBoxDetailData({
<View>
<TextCustom size="small">Target Dana</TextCustom>
<TextCustom truncate={2} size="large" bold color="yellow">
Rp. 7.500.000
Rp. {formatCurrencyDisplay(data?.target) || "-"}
</TextCustom>
</View>
</Grid.Col>
@@ -39,7 +43,7 @@ export default function Donation_ComponentBoxDetailData({
<View>
<TextCustom size="small">Kategori</TextCustom>
<TextCustom size="large" bold color="yellow">
Kegiatan Sosial
{data?.DonasiMaster_Ketegori?.name || "-"}
</TextCustom>
</View>
</Grid.Col>

View File

@@ -5,8 +5,10 @@ import { Ionicons } from "@expo/vector-icons";
export default function Donation_ComponentStoryFunrising({
id,
dataStory,
}: {
id: string;
dataStory: any;
}) {
return (
<>
@@ -29,12 +31,7 @@ export default function Donation_ComponentStoryFunrising({
/>
</Grid.Col>
</Grid>
<TextCustom truncate={3}>
Lorem ipsum dolor sit amet consectetur adipisicing elit. Totam,
iusto porro quae optio accusantium amet minima deleniti temporibus
cum voluptatem vel veniam doloribus blanditiis sapiente deserunt
distinctio eaque aliquid laboriosam?
</TextCustom>
<TextCustom truncate={3}>{dataStory?.pembukaan || "-"}</TextCustom>
</StackCustom>
</BaseBox>
</>

View File

@@ -1,22 +1,82 @@
import { apiConfig } from "../api-config";
export async function apiDonationCreate({ data , category}: { data: any , category: "temporary" | "permanent"}) {
export async function apiDonationCreate({
data,
category,
}: {
data: any;
category: "temporary" | "permanent";
}) {
try {
const response = await apiConfig.post(`/mobile/donation?category=${category}`, {
data: data,
});
const response = await apiConfig.post(
`/mobile/donation?category=${category}`,
{
data: data,
}
);
return response.data;
} catch (error) {
throw error;
}
}
export async function apiDonationGetOne({ id, category }: { id: string , category: "temporary" | "permanent"}) {
export async function apiDonationGetOne({
id,
category,
}: {
id: string;
category: "temporary" | "permanent";
}) {
try {
const response = await apiConfig.get(`/mobile/donation/${id}?category=${category}`);
const response = await apiConfig.get(
`/mobile/donation/${id}?category=${category}`
);
return response.data;
} catch (error) {
throw error;
}
}
export async function apiDonationGetByStatus({
authorId,
status,
}: {
authorId: string;
status: string;
}) {
const authorQuery = `/${authorId}`;
const statusQuery = `/${status}`;
try {
const response = await apiConfig.get(
`/mobile/donation${authorQuery}${statusQuery}`
);
return response.data;
} catch (error) {
throw error;
}
}
export async function apiDonationUpdateStatus({
id,
status,
}: {
id: string;
status: "draft" | "review" | "publish" | "reject";
}) {
try {
const response = await apiConfig.put(`/mobile/donation/${id}/${status}`);
return response.data;
} catch (error) {
throw error;
}
}
export async function apiDonationDelete({ id }: { id: string }) {
try {
const response = await apiConfig.delete(`/mobile/donation/${id}`);
return response.data;
} catch (error) {
throw error;
}
}