Compare commits
7 Commits
loaddata/4
...
loaddata/1
| Author | SHA1 | Date | |
|---|---|---|---|
| b2be7be533 | |||
| 2705f96b01 | |||
| 38a6b424e8 | |||
| 83fa277e03 | |||
| c570a19d84 | |||
| 7415c8c8ce | |||
| 72a3d42013 |
169
QWEN.md
169
QWEN.md
@@ -0,0 +1,169 @@
|
||||
# HIPMI Mobile Application - Development Context
|
||||
|
||||
## Project Overview
|
||||
|
||||
HIPMI Mobile is a cross-platform mobile application built with Expo and React Native. The application is named "HIPMI Badung Connect" and serves as a platform for the HIPMI (Himpunan Pengusaha dan Pengusaha Indonesia) Badung chapter. It's designed to run on iOS, Android, and web platforms using a single codebase.
|
||||
|
||||
### Key Technologies
|
||||
- **Framework**: Expo (v54.0.0) with React Native (v0.81.4)
|
||||
- **Language**: TypeScript
|
||||
- **Architecture**: File-based routing with Expo Router
|
||||
- **State Management**: Context API
|
||||
- **UI Components**: React Native Paper, custom components
|
||||
- **Maps Integration**: Mapbox Maps for React Native
|
||||
- **Push Notifications**: React Native Firebase Messaging
|
||||
- **Build System**: Metro bundler
|
||||
|
||||
### Project Structure
|
||||
```
|
||||
hipmi-mobile/
|
||||
├── app/ # Main application screens and routing
|
||||
│ ├── _layout.tsx # Root layout component
|
||||
│ ├── index.tsx # Entry point (Login screen)
|
||||
│ └── ...
|
||||
├── components/ # Reusable UI components
|
||||
├── context/ # State management (AuthContext)
|
||||
├── screens/ # Screen components organized by feature
|
||||
│ ├── Admin/ # Admin panel screens
|
||||
│ ├── Authentication/ # Login, registration flows
|
||||
│ ├── Collaboration/ # Collaboration features
|
||||
│ ├── Event/ # Event management
|
||||
│ ├── Forum/ # Forum functionality
|
||||
│ ├── Home/ # Home screen
|
||||
│ ├── Maps/ # Map integration
|
||||
│ ├── Profile/ # User profile
|
||||
│ └── ...
|
||||
├── assets/ # Images, icons, and static assets
|
||||
├── constants/ # Constants and configuration values
|
||||
├── hooks/ # Custom React hooks
|
||||
├── lib/ # Utility libraries
|
||||
├── navigation/ # Navigation configuration
|
||||
├── service/ # API services and business logic
|
||||
├── types/ # TypeScript type definitions
|
||||
└── utils/ # Helper functions
|
||||
```
|
||||
|
||||
## Building and Running
|
||||
|
||||
### Prerequisites
|
||||
- Node.js (with bun as the package manager)
|
||||
- Expo CLI
|
||||
- iOS Simulator or Android Emulator (for native builds)
|
||||
|
||||
### Setup and Development
|
||||
|
||||
1. **Install Dependencies**
|
||||
```bash
|
||||
bun install
|
||||
```
|
||||
|
||||
2. **Run Development Server**
|
||||
```bash
|
||||
bun run start
|
||||
```
|
||||
Or use the shorthand:
|
||||
```bash
|
||||
bunx expo start
|
||||
```
|
||||
|
||||
3. **Platform-Specific Commands**
|
||||
- iOS: `bun run ios` or `bunx expo start --ios`
|
||||
- Android: `bun run android` or `bunx expo start --android`
|
||||
- Web: `bun run web` or `bunx expo start --web`
|
||||
|
||||
4. **Linting**
|
||||
```bash
|
||||
bun run lint
|
||||
```
|
||||
|
||||
### Environment Variables
|
||||
The application uses environment variables defined in the app.config.js file:
|
||||
- `API_BASE_URL`: Base URL for API endpoints
|
||||
- `BASE_URL`: Base application URL
|
||||
- `DEEP_LINK_URL`: URL for deep linking functionality
|
||||
|
||||
### EAS Build Configuration
|
||||
The project uses Expo Application Services (EAS) for building and deploying:
|
||||
- Development builds with development client
|
||||
- Preview builds for internal distribution
|
||||
- Production builds for app stores
|
||||
|
||||
## Features and Functionality
|
||||
|
||||
The application appears to include several key modules:
|
||||
- **Authentication**: Login, registration, and verification flows
|
||||
- **Admin Panel**: Administrative functions
|
||||
- **Collaboration**: Tools for member collaboration
|
||||
- **Events**: Event management and calendar
|
||||
- **Forum**: Discussion forums
|
||||
- **Maps**: Location-based services with Mapbox integration
|
||||
- **Donations**: Donation functionality
|
||||
- **Job Board**: Employment opportunities
|
||||
- **Investment**: Investment-related features
|
||||
- **Voting**: Voting systems
|
||||
- **Portfolio**: Member portfolio showcase
|
||||
- **Notifications**: Push notifications via Firebase
|
||||
|
||||
## Development Conventions
|
||||
|
||||
### Coding Standards
|
||||
- TypeScript is used throughout the project for type safety
|
||||
- Component-based architecture with reusable components
|
||||
- Context API for state management
|
||||
- File-based routing with Expo Router
|
||||
- Consistent naming conventions using camelCase for variables and PascalCase for components
|
||||
|
||||
### Testing
|
||||
- Linting is configured with ESLint
|
||||
- Standard Expo linting configuration is used
|
||||
|
||||
### Security
|
||||
- Firebase is integrated for authentication and messaging
|
||||
- Camera and location permissions are properly configured
|
||||
- Deep linking is secured with app domain associations
|
||||
|
||||
## Key Dependencies
|
||||
|
||||
### Core Dependencies
|
||||
- `@react-navigation/*`: Navigation solution for React Native
|
||||
- `@react-native-firebase/*`: Firebase integration for React Native
|
||||
- `@rnmapbox/maps`: Mapbox integration for React Native
|
||||
- `expo-router`: File-based routing for Expo applications
|
||||
- `react-native-paper`: Material Design components for React Native
|
||||
- `react-native-toast-message`: Toast notifications
|
||||
- `react-native-otp-entry`: OTP input components
|
||||
- `react-native-qrcode-svg`: QR code generation
|
||||
|
||||
### Development Dependencies
|
||||
- `@types/*`: TypeScript type definitions
|
||||
- `eslint-config-expo`: Expo-specific ESLint configuration
|
||||
- `typescript`: Type checking
|
||||
|
||||
## Platform Support
|
||||
|
||||
The application is configured to support:
|
||||
- **iOS**: With tablet support and proper permissions
|
||||
- **Android**: With adaptive icons and intent filters for deep linking
|
||||
- **Web**: Static output configuration for web deployment
|
||||
|
||||
## Special Configurations
|
||||
|
||||
### iOS Configuration
|
||||
- Bundle identifier: `com.anonymous.hipmi-mobile`
|
||||
- Supports tablets
|
||||
- Google Services integration
|
||||
- Location permission handling
|
||||
- Associated domains for deep linking
|
||||
|
||||
### Android Configuration
|
||||
- Package name: `com.bip.hipmimobileapp`
|
||||
- Adaptive icons
|
||||
- Edge-to-edge display enabled
|
||||
- Intent filters for HTTPS deep linking
|
||||
- Google Services integration
|
||||
|
||||
### Maps Integration
|
||||
The application uses Mapbox for mapping functionality with the `@rnmapbox/maps` plugin.
|
||||
|
||||
### Push Notifications
|
||||
Firebase Cloud Messaging is integrated for push notifications with proper configuration for both iOS and Android platforms.
|
||||
@@ -1,56 +1,9 @@
|
||||
import {
|
||||
FloatingButton,
|
||||
LoaderCustom,
|
||||
TextCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import Donation_BoxPublish from "@/screens/Donation/BoxPublish";
|
||||
import { apiDonationGetAll } from "@/service/api-client/api-donation";
|
||||
import { router, useFocusEffect } from "expo-router";
|
||||
import _ from "lodash";
|
||||
import { useCallback, useState } from "react";
|
||||
import Donation_ScreenBeranda from "@/screens/Donation/ScreenBeranda";
|
||||
|
||||
export default function DonationBeranda() {
|
||||
const [list, setList] = useState<any[] | null>(null);
|
||||
const [loadList, setLoadList] = useState(false);
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
onLoadData();
|
||||
}, [])
|
||||
);
|
||||
|
||||
const onLoadData = async () => {
|
||||
try {
|
||||
setLoadList(true);
|
||||
const response = await apiDonationGetAll({
|
||||
category: "beranda"
|
||||
});
|
||||
|
||||
setList(response.data);
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
} finally {
|
||||
setLoadList(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<ViewWrapper
|
||||
hideFooter
|
||||
floatingButton={
|
||||
<FloatingButton onPress={() => router.push("/donation/create")} />
|
||||
}
|
||||
>
|
||||
{loadList ? (
|
||||
<LoaderCustom />
|
||||
) : _.isEmpty(list) ? (
|
||||
<TextCustom align="center" color="gray">Belum ada donasi</TextCustom>
|
||||
) : (
|
||||
list?.map((item: any, index: number) => (
|
||||
<Donation_BoxPublish data={item} key={index} id={item.id} />
|
||||
))
|
||||
)}
|
||||
</ViewWrapper>
|
||||
<>
|
||||
<Donation_ScreenBeranda />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,148 +1,5 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
BadgeCustom,
|
||||
BaseBox,
|
||||
DummyLandscapeImage,
|
||||
Grid,
|
||||
LoaderCustom,
|
||||
StackCustom,
|
||||
TextCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
import { apiDonationGetAll } from "@/service/api-client/api-donation";
|
||||
import { formatCurrencyDisplay } from "@/utils/formatCurrencyDisplay";
|
||||
import { Href, router, useFocusEffect } from "expo-router";
|
||||
import _ from "lodash";
|
||||
import { useCallback, useState } from "react";
|
||||
import { View } from "react-native";
|
||||
import Toast from "react-native-toast-message";
|
||||
import Donation_ScreenMyDonation from "@/screens/Donation/ScreenMyDonation";
|
||||
|
||||
export default function DonationMyDonation() {
|
||||
const { user } = useAuth();
|
||||
const [list, setList] = useState<any[] | null>(null);
|
||||
const [loadList, setLoadList] = useState(false);
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
onLoadData();
|
||||
}, [user?.id]),
|
||||
);
|
||||
|
||||
const onLoadData = async () => {
|
||||
if (!user?.id) {
|
||||
Toast.show({
|
||||
type: "error",
|
||||
text1: "Load data gagal, user tidak ditemukan",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
setLoadList(true);
|
||||
const response = await apiDonationGetAll({
|
||||
category: "my-donation",
|
||||
authorId: user?.id,
|
||||
});
|
||||
|
||||
|
||||
setList(response.data);
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
} finally {
|
||||
setLoadList(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handlerColor = (status: string) => {
|
||||
if (status === "menunggu") {
|
||||
return "orange";
|
||||
} else if (status === "proses") {
|
||||
return "white";
|
||||
} else if (status === "berhasil") {
|
||||
return "green";
|
||||
} else if (status === "gagal") {
|
||||
return "red";
|
||||
}
|
||||
};
|
||||
|
||||
const handlePress = ({
|
||||
invoiceId,
|
||||
donationId,
|
||||
status,
|
||||
}: {
|
||||
invoiceId: string;
|
||||
donationId: string;
|
||||
status: string;
|
||||
}) => {
|
||||
const url: Href = `../${donationId}/(transaction-flow)/${invoiceId}`;
|
||||
if (status === "menunggu") {
|
||||
router.push(`${url}/invoice`);
|
||||
} else if (status === "proses") {
|
||||
router.push(`${url}/process`);
|
||||
} else if (status === "berhasil") {
|
||||
router.push(`${url}/success`);
|
||||
} else if (status === "gagal") {
|
||||
router.push(`${url}/failed`);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<ViewWrapper hideFooter>
|
||||
{loadList ? (
|
||||
<LoaderCustom />
|
||||
) : _.isEmpty(list) ? (
|
||||
<TextCustom align="center" color="gray">
|
||||
Belum ada transaksi
|
||||
</TextCustom>
|
||||
) : (
|
||||
list?.map((item, index) => (
|
||||
<BaseBox
|
||||
key={index}
|
||||
paddingTop={7}
|
||||
paddingBottom={7}
|
||||
onPress={() => {
|
||||
handlePress({
|
||||
status: _.lowerCase(item.statusInvoice),
|
||||
invoiceId: item.id,
|
||||
donationId: item.donasiId,
|
||||
});
|
||||
}}
|
||||
>
|
||||
<Grid>
|
||||
<Grid.Col span={5}>
|
||||
<DummyLandscapeImage
|
||||
height={100}
|
||||
unClickPath
|
||||
imageId={item.imageId}
|
||||
/>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={1}>
|
||||
<View />
|
||||
</Grid.Col>
|
||||
<Grid.Col span={6}>
|
||||
<StackCustom>
|
||||
<TextCustom truncate={2} bold>
|
||||
{item.title || "-"}
|
||||
</TextCustom>
|
||||
|
||||
<TextCustom bold color="yellow">
|
||||
Rp. {formatCurrencyDisplay(item.nominal)}
|
||||
</TextCustom>
|
||||
|
||||
<BadgeCustom
|
||||
variant="light"
|
||||
color={handlerColor(_.lowerCase(item.statusInvoice))}
|
||||
fullWidth
|
||||
>
|
||||
{item.statusInvoice}
|
||||
</BadgeCustom>
|
||||
</StackCustom>
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
</BaseBox>
|
||||
))
|
||||
)}
|
||||
</ViewWrapper>
|
||||
);
|
||||
return <Donation_ScreenMyDonation />;
|
||||
}
|
||||
|
||||
@@ -1,82 +1,10 @@
|
||||
/* 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 { apiDonationGetByStatus } from "@/service/api-client/api-donation";
|
||||
import { useFocusEffect, useLocalSearchParams } from "expo-router";
|
||||
import _ from "lodash";
|
||||
import { useCallback, useState } from "react";
|
||||
import Donation_ScreenStatus from "@/screens/Donation/ScreenStatus";
|
||||
import { useLocalSearchParams } from "expo-router";
|
||||
|
||||
export default function DonationStatus() {
|
||||
const { user } = useAuth();
|
||||
const { status } = useLocalSearchParams<{ status?: string }>();
|
||||
|
||||
const [activeCategory, setActiveCategory] = useState<string | null>(
|
||||
status || "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);
|
||||
// tambahkan logika lain seperti filter dsb.
|
||||
};
|
||||
|
||||
const scrollComponent = (
|
||||
<ScrollableCustom
|
||||
data={dummyMasterStatus.map((e, i) => ({
|
||||
id: i,
|
||||
label: e.label,
|
||||
value: e.value,
|
||||
}))}
|
||||
onButtonPress={handlePress}
|
||||
activeId={activeCategory as any}
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<ViewWrapper hideFooter headerComponent={scrollComponent}>
|
||||
{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>
|
||||
<Donation_ScreenStatus initialStatus={status || "publish"} />
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
BoxButtonOnFooter,
|
||||
ButtonCenteredOnly,
|
||||
ButtonCustom,
|
||||
InformationBox,
|
||||
@@ -31,7 +32,7 @@ export default function DonationEditNews() {
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
onLoadData();
|
||||
}, [news])
|
||||
}, [news]),
|
||||
);
|
||||
|
||||
const onLoadData = async () => {
|
||||
@@ -104,7 +105,21 @@ export default function DonationEditNews() {
|
||||
};
|
||||
|
||||
return (
|
||||
<ViewWrapper>
|
||||
<ViewWrapper
|
||||
footerComponent={
|
||||
<BoxButtonOnFooter>
|
||||
<ButtonCustom
|
||||
disabled={!data?.title || !data?.deskripsi}
|
||||
isLoading={isLoading}
|
||||
onPress={() => {
|
||||
handlerSubmitUpdate();
|
||||
}}
|
||||
>
|
||||
Update
|
||||
</ButtonCustom>
|
||||
</BoxButtonOnFooter>
|
||||
}
|
||||
>
|
||||
<StackCustom gap={"xs"}>
|
||||
<InformationBox text="Upload gambar bersifat opsional untuk melengkapi kabar terkait donasi Anda." />
|
||||
<LandscapeFrameUploaded
|
||||
@@ -148,15 +163,6 @@ export default function DonationEditNews() {
|
||||
/>
|
||||
|
||||
<Spacing />
|
||||
<ButtonCustom
|
||||
disabled={!data?.title || !data?.deskripsi}
|
||||
isLoading={isLoading}
|
||||
onPress={() => {
|
||||
handlerSubmitUpdate();
|
||||
}}
|
||||
>
|
||||
Update
|
||||
</ButtonCustom>
|
||||
</StackCustom>
|
||||
<Spacing />
|
||||
</ViewWrapper>
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import {
|
||||
BoxButtonOnFooter,
|
||||
ButtonCenteredOnly,
|
||||
ButtonCustom,
|
||||
InformationBox,
|
||||
LandscapeFrameUploaded,
|
||||
NewWrapper,
|
||||
Spacing,
|
||||
StackCustom,
|
||||
TextAreaCustom,
|
||||
@@ -53,7 +55,7 @@ export default function DonationAddNews() {
|
||||
text1: "Gagal menambah berita",
|
||||
});
|
||||
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
Toast.show({
|
||||
@@ -70,7 +72,21 @@ export default function DonationAddNews() {
|
||||
};
|
||||
|
||||
return (
|
||||
<ViewWrapper>
|
||||
<NewWrapper
|
||||
footerComponent={
|
||||
<BoxButtonOnFooter>
|
||||
<ButtonCustom
|
||||
disabled={!data.title || !data.deskripsi}
|
||||
isLoading={isLoading}
|
||||
onPress={() => {
|
||||
handlerSubmit();
|
||||
}}
|
||||
>
|
||||
Simpan
|
||||
</ButtonCustom>
|
||||
</BoxButtonOnFooter>
|
||||
}
|
||||
>
|
||||
<StackCustom gap={"xs"}>
|
||||
<InformationBox text="Upload gambar bersifat opsional untuk melengkapi kabar terkait donasi Anda." />
|
||||
<LandscapeFrameUploaded image={image?.uri} />
|
||||
@@ -116,17 +132,7 @@ export default function DonationAddNews() {
|
||||
/>
|
||||
|
||||
<Spacing />
|
||||
<ButtonCustom
|
||||
disabled={!data.title || !data.deskripsi}
|
||||
isLoading={isLoading}
|
||||
onPress={() => {
|
||||
handlerSubmit();
|
||||
}}
|
||||
>
|
||||
Simpan
|
||||
</ButtonCustom>
|
||||
</StackCustom>
|
||||
<Spacing />
|
||||
</ViewWrapper>
|
||||
</NewWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,110 +1,8 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
BackButton,
|
||||
BaseBox,
|
||||
DrawerCustom,
|
||||
Grid,
|
||||
LoaderCustom,
|
||||
MenuDrawerDynamicGrid,
|
||||
TextCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import { IconPlus } from "@/components/_Icon";
|
||||
import { apiDonationGetNewsById } from "@/service/api-client/api-donation";
|
||||
import { formatChatTime } from "@/utils/formatChatTime";
|
||||
import {
|
||||
router,
|
||||
Stack,
|
||||
useFocusEffect,
|
||||
useLocalSearchParams,
|
||||
} from "expo-router";
|
||||
import _ from "lodash";
|
||||
import { useCallback, useState } from "react";
|
||||
import { useLocalSearchParams } from "expo-router";
|
||||
import Donation_ScreenListOfNews from "@/screens/Donation/ScreenListOfNews";
|
||||
|
||||
export default function DonationRecapOfNews() {
|
||||
const { id } = useLocalSearchParams();
|
||||
const [openDrawer, setOpenDrawer] = useState(false);
|
||||
const [list, setList] = useState<any[] | null>(null);
|
||||
const [loadList, setLoadList] = useState<boolean>(false);
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
onLoadList();
|
||||
}, [id])
|
||||
);
|
||||
|
||||
const onLoadList = async () => {
|
||||
try {
|
||||
setLoadList(true);
|
||||
const response = await apiDonationGetNewsById({
|
||||
id: id as string,
|
||||
category: "get-all",
|
||||
});
|
||||
|
||||
setList(response.data);
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
setList([]);
|
||||
} finally {
|
||||
setLoadList(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Stack.Screen
|
||||
options={{
|
||||
title: "Daftar Kabar",
|
||||
headerLeft: () => <BackButton />,
|
||||
}}
|
||||
/>
|
||||
<ViewWrapper>
|
||||
{loadList ? (
|
||||
<LoaderCustom />
|
||||
) : _.isEmpty(list) ? (
|
||||
<TextCustom align="center" color="gray">
|
||||
Tidak ada kabar
|
||||
</TextCustom>
|
||||
) : (
|
||||
list?.map((item: any, index: number) => (
|
||||
<BaseBox key={index} href={`/donation/[id]/(news)/${item.id}`}>
|
||||
<Grid>
|
||||
<Grid.Col span={8}>
|
||||
<TextCustom truncate bold>
|
||||
{item?.title || "-"}
|
||||
</TextCustom>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={4} style={{ alignItems: "flex-end" }}>
|
||||
<TextCustom size="small">
|
||||
{formatChatTime(item?.createdAt)}
|
||||
</TextCustom>
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
</BaseBox>
|
||||
))
|
||||
)}
|
||||
</ViewWrapper>
|
||||
|
||||
<DrawerCustom
|
||||
isVisible={openDrawer}
|
||||
closeDrawer={() => setOpenDrawer(false)}
|
||||
height={"auto"}
|
||||
>
|
||||
<MenuDrawerDynamicGrid
|
||||
data={[
|
||||
{
|
||||
icon: <IconPlus />,
|
||||
label: "Tambah Berita",
|
||||
path: `/donation/${id}/(news)/add-news`,
|
||||
},
|
||||
]}
|
||||
onPressItem={(item) => {
|
||||
console.log("PATH ", item.path);
|
||||
router.navigate(item.path as any);
|
||||
setOpenDrawer(false);
|
||||
}}
|
||||
/>
|
||||
</DrawerCustom>
|
||||
</>
|
||||
);
|
||||
|
||||
return <Donation_ScreenListOfNews donationId={id as string} />;
|
||||
}
|
||||
|
||||
@@ -1,112 +1,8 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
BackButton,
|
||||
BaseBox,
|
||||
DotButton,
|
||||
DrawerCustom,
|
||||
Grid,
|
||||
LoaderCustom,
|
||||
MenuDrawerDynamicGrid,
|
||||
TextCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import { IconPlus } from "@/components/_Icon";
|
||||
import { apiDonationGetNewsById } from "@/service/api-client/api-donation";
|
||||
import { formatChatTime } from "@/utils/formatChatTime";
|
||||
import {
|
||||
router,
|
||||
Stack,
|
||||
useFocusEffect,
|
||||
useLocalSearchParams,
|
||||
} from "expo-router";
|
||||
import _ from "lodash";
|
||||
import { useCallback, useState } from "react";
|
||||
import { useLocalSearchParams } from "expo-router";
|
||||
import Donation_ScreenRecapOfNews from "@/screens/Donation/ScreenRecapOfNews";
|
||||
|
||||
export default function DonationRecapOfNews() {
|
||||
const { id } = useLocalSearchParams();
|
||||
const [openDrawer, setOpenDrawer] = useState(false);
|
||||
const [list, setList] = useState<any[] | null>(null);
|
||||
const [loadList, setLoadList] = useState<boolean>(false);
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
onLoadList();
|
||||
}, [id])
|
||||
);
|
||||
|
||||
const onLoadList = async () => {
|
||||
try {
|
||||
setLoadList(true);
|
||||
const response = await apiDonationGetNewsById({
|
||||
id: id as string,
|
||||
category: "get-all",
|
||||
});
|
||||
|
||||
setList(response.data);
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
setList([]);
|
||||
} finally {
|
||||
setLoadList(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Stack.Screen
|
||||
options={{
|
||||
title: "Rekap Kabar",
|
||||
headerLeft: () => <BackButton />,
|
||||
headerRight: () => <DotButton onPress={() => setOpenDrawer(true)} />,
|
||||
}}
|
||||
/>
|
||||
<ViewWrapper>
|
||||
{loadList ? (
|
||||
<LoaderCustom />
|
||||
) : _.isEmpty(list) ? (
|
||||
<TextCustom align="center" color="gray">
|
||||
Tidak ada kabar
|
||||
</TextCustom>
|
||||
) : (
|
||||
list?.map((item: any, index: number) => (
|
||||
<BaseBox key={index} href={`/donation/[id]/(news)/${item.id}`}>
|
||||
<Grid>
|
||||
<Grid.Col span={8}>
|
||||
<TextCustom truncate bold>
|
||||
{item?.title || "-"}
|
||||
</TextCustom>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={4} style={{ alignItems: "flex-end" }}>
|
||||
<TextCustom size="small">
|
||||
{formatChatTime(item?.createdAt)}
|
||||
</TextCustom>
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
</BaseBox>
|
||||
))
|
||||
)}
|
||||
</ViewWrapper>
|
||||
|
||||
<DrawerCustom
|
||||
isVisible={openDrawer}
|
||||
closeDrawer={() => setOpenDrawer(false)}
|
||||
height={"auto"}
|
||||
>
|
||||
<MenuDrawerDynamicGrid
|
||||
data={[
|
||||
{
|
||||
icon: <IconPlus />,
|
||||
label: "Tambah Berita",
|
||||
path: `/donation/${id}/(news)/add-news`,
|
||||
},
|
||||
]}
|
||||
onPressItem={(item) => {
|
||||
console.log("PATH ", item.path);
|
||||
router.navigate(item.path as any);
|
||||
setOpenDrawer(false);
|
||||
}}
|
||||
/>
|
||||
</DrawerCustom>
|
||||
</>
|
||||
);
|
||||
|
||||
return <Donation_ScreenRecapOfNews donationId={id as string} />;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
BaseBox,
|
||||
BoxButtonOnFooter,
|
||||
ButtonCenteredOnly,
|
||||
ButtonCustom,
|
||||
Grid,
|
||||
@@ -35,7 +36,7 @@ export default function DonationInvoice() {
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
onLoadData();
|
||||
}, [invoiceId])
|
||||
}, [invoiceId]),
|
||||
);
|
||||
|
||||
const onLoadData = async () => {
|
||||
@@ -100,7 +101,22 @@ export default function DonationInvoice() {
|
||||
|
||||
return (
|
||||
<>
|
||||
<ViewWrapper>
|
||||
<ViewWrapper
|
||||
hideFooter
|
||||
footerComponent={
|
||||
<BoxButtonOnFooter>
|
||||
<ButtonCustom
|
||||
disabled={!image}
|
||||
isLoading={isLoading}
|
||||
onPress={() => {
|
||||
handlerUpdateInvoice();
|
||||
}}
|
||||
>
|
||||
Simpan
|
||||
</ButtonCustom>
|
||||
</BoxButtonOnFooter>
|
||||
}
|
||||
>
|
||||
<StackCustom>
|
||||
<InformationBox
|
||||
text={`Mohon transfer donasi anda ke rekening dibawah`}
|
||||
@@ -204,16 +220,6 @@ export default function DonationInvoice() {
|
||||
</ButtonCenteredOnly>
|
||||
</StackCustom>
|
||||
</BaseBox>
|
||||
|
||||
<ButtonCustom
|
||||
disabled={!image}
|
||||
isLoading={isLoading}
|
||||
onPress={() => {
|
||||
handlerUpdateInvoice();
|
||||
}}
|
||||
>
|
||||
Simpan
|
||||
</ButtonCustom>
|
||||
</StackCustom>
|
||||
<Spacing />
|
||||
</ViewWrapper>
|
||||
|
||||
@@ -4,11 +4,12 @@ import {
|
||||
DotButton,
|
||||
DrawerCustom,
|
||||
MenuDrawerDynamicGrid,
|
||||
NewWrapper,
|
||||
Spacing,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import { IconEdit, IconNews } from "@/components/_Icon";
|
||||
import { IMenuDrawerItem } from "@/components/_Interface/types";
|
||||
import CustomSkeleton from "@/components/_ShareComponent/SkeletonCustom";
|
||||
import { MainColor } from "@/constants/color-palet";
|
||||
import { ICON_SIZE_SMALL } from "@/constants/constans-value";
|
||||
import Donation_ButtonStatusSection from "@/screens/Donation/ButtonStatusSection";
|
||||
@@ -26,18 +27,19 @@ import {
|
||||
} from "expo-router";
|
||||
import _ from "lodash";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { RefreshControl } from "react-native";
|
||||
|
||||
export default function DonasiDetailStatus() {
|
||||
const { id, status } = useLocalSearchParams();
|
||||
const [openDrawer, setOpenDrawer] = useState(false);
|
||||
const [openDrawerPublish, setOpenDrawerPublish] = useState(false);
|
||||
|
||||
const [data, setData] = useState<any>();
|
||||
const [refreshing, setRefreshing] = useState(false);
|
||||
const [data, setData] = useState<any | null>(null);
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
onLoadData();
|
||||
}, [id])
|
||||
}, [id]),
|
||||
);
|
||||
|
||||
const onLoadData = async () => {
|
||||
@@ -80,6 +82,17 @@ export default function DonasiDetailStatus() {
|
||||
});
|
||||
};
|
||||
|
||||
const onRefresh = useCallback(() => {
|
||||
try {
|
||||
setRefreshing(true);
|
||||
onLoadData();
|
||||
} catch (error) {
|
||||
console.log("Error refresh");
|
||||
} finally {
|
||||
setRefreshing(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Stack.Screen
|
||||
@@ -94,31 +107,50 @@ export default function DonasiDetailStatus() {
|
||||
) : null,
|
||||
}}
|
||||
/>
|
||||
<ViewWrapper>
|
||||
<Donation_ComponentBoxDetailData
|
||||
sisaHari={value.sisa}
|
||||
reminder={value.reminder}
|
||||
data={data}
|
||||
bottomSection={
|
||||
status === "publish" && (
|
||||
<Donation_ProgressSection
|
||||
<NewWrapper
|
||||
refreshControl={
|
||||
<RefreshControl
|
||||
refreshing={refreshing}
|
||||
onRefresh={onRefresh}
|
||||
tintColor={MainColor.yellow}
|
||||
colors={[MainColor.yellow]}
|
||||
/>
|
||||
}
|
||||
>
|
||||
{!data ? (
|
||||
<CustomSkeleton height={400} />
|
||||
) : (
|
||||
<>
|
||||
<Donation_ComponentBoxDetailData
|
||||
sisaHari={value.sisa}
|
||||
reminder={value.reminder}
|
||||
data={data}
|
||||
showSisaHari={status === "publish" ? true : false}
|
||||
bottomSection={
|
||||
status === "publish" && (
|
||||
<Donation_ProgressSection
|
||||
id={id as string}
|
||||
progres={Number(data?.progres) || 0}
|
||||
/>
|
||||
)
|
||||
}
|
||||
/>
|
||||
<Donation_ComponentStoryFunrising
|
||||
id={id as string}
|
||||
dataStory={data?.CeritaDonasi}
|
||||
/>
|
||||
<Spacing />
|
||||
{data && (
|
||||
<Donation_ButtonStatusSection
|
||||
id={id as string}
|
||||
progres={Number(data?.progres) || 0}
|
||||
status={status as string}
|
||||
/>
|
||||
)
|
||||
}
|
||||
/>
|
||||
<Donation_ComponentStoryFunrising
|
||||
id={id as string}
|
||||
dataStory={data?.CeritaDonasi}
|
||||
/>
|
||||
<Spacing />
|
||||
<Donation_ButtonStatusSection
|
||||
id={id as string}
|
||||
status={status as string}
|
||||
/>
|
||||
<Spacing />
|
||||
</ViewWrapper>
|
||||
)}
|
||||
|
||||
<Spacing />
|
||||
</>
|
||||
)}
|
||||
</NewWrapper>
|
||||
|
||||
<DrawerCustom
|
||||
isVisible={openDrawer}
|
||||
|
||||
@@ -1,16 +1,19 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
BoxButtonOnFooter,
|
||||
ButtonCenteredOnly,
|
||||
ButtonCustom,
|
||||
InformationBox,
|
||||
LandscapeFrameUploaded,
|
||||
LoaderCustom,
|
||||
NewWrapper,
|
||||
SelectCustom,
|
||||
Spacing,
|
||||
StackCustom,
|
||||
TextInputCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import ListSkeletonComponent from "@/components/_ShareComponent/ListSkeletonComponent";
|
||||
import API_IMAGE from "@/constants/api-storage";
|
||||
import DIRECTORY_ID from "@/constants/directory-id";
|
||||
import {
|
||||
@@ -60,7 +63,7 @@ export default function DonationEdit() {
|
||||
useCallback(() => {
|
||||
onLoadData();
|
||||
onLoadList();
|
||||
}, [id])
|
||||
}, [id]),
|
||||
);
|
||||
|
||||
const onLoadData = async () => {
|
||||
@@ -79,7 +82,6 @@ export default function DonationEdit() {
|
||||
imageId: response.data.imageId,
|
||||
});
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
}
|
||||
@@ -182,10 +184,24 @@ export default function DonationEdit() {
|
||||
};
|
||||
|
||||
return (
|
||||
<ViewWrapper>
|
||||
<NewWrapper
|
||||
hideFooter
|
||||
footerComponent={
|
||||
<BoxButtonOnFooter>
|
||||
<ButtonCustom
|
||||
isLoading={isLoading}
|
||||
onPress={() => {
|
||||
handlerSubmitUpdate();
|
||||
}}
|
||||
>
|
||||
Update
|
||||
</ButtonCustom>
|
||||
</BoxButtonOnFooter>
|
||||
}
|
||||
>
|
||||
<InformationBox text="Lengkapi semua data di bawah untuk selanjutnya mengisi cerita penggalangan dana." />
|
||||
{!data || loadList ? (
|
||||
<LoaderCustom />
|
||||
<ListSkeletonComponent />
|
||||
) : (
|
||||
<StackCustom gap={"xs"}>
|
||||
<TextInputCustom
|
||||
@@ -260,17 +276,9 @@ export default function DonationEdit() {
|
||||
/>
|
||||
|
||||
<Spacing />
|
||||
<ButtonCustom
|
||||
isLoading={isLoading}
|
||||
onPress={() => {
|
||||
handlerSubmitUpdate();
|
||||
}}
|
||||
>
|
||||
Update
|
||||
</ButtonCustom>
|
||||
</StackCustom>
|
||||
)}
|
||||
<Spacing />
|
||||
</ViewWrapper>
|
||||
</NewWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,124 +1,8 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
BaseBox,
|
||||
ButtonCenteredOnly,
|
||||
Grid,
|
||||
InformationBox,
|
||||
LoaderCustom,
|
||||
StackCustom,
|
||||
TextCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import {
|
||||
apiDonationDisbursementOfFundsListById,
|
||||
apiDonationGetOne,
|
||||
} from "@/service/api-client/api-donation";
|
||||
import { formatCurrencyDisplay } from "@/utils/formatCurrencyDisplay";
|
||||
import dayjs from "dayjs";
|
||||
import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
|
||||
import _ from "lodash";
|
||||
import React, { useState } from "react";
|
||||
import { useLocalSearchParams } from "expo-router";
|
||||
import Donation_ScreenFundDisbursement from "@/screens/Donation/ScreenFundDisbursement";
|
||||
|
||||
export default function DonationFundDisbursement() {
|
||||
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 (
|
||||
<>
|
||||
<ViewWrapper>
|
||||
<InformationBox text="Pencairan dana akan dilakukan oleh Admin HIPMI tanpa campur tangan pihak manapun, jika berita pencairan dana dibawah tidak sesuai dengan kabar yang diberikan oleh PENGGALANG DANA. Maka pegguna lain dapat melaporkannya pada Admin HIPMI !" />
|
||||
<BaseBox>
|
||||
<Grid>
|
||||
<Grid.Col span={6}>
|
||||
<TextCustom bold color="yellow">
|
||||
Rp. {formatCurrencyDisplay(data?.totalPencairan)}
|
||||
</TextCustom>
|
||||
<TextCustom size="small">Total Pencairan Dana</TextCustom>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={6}>
|
||||
<TextCustom bold color="yellow">
|
||||
{data?.akumulasiPencairan} kali
|
||||
</TextCustom>
|
||||
<TextCustom size="small">Akumulasi Pencairan</TextCustom>
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
</BaseBox>
|
||||
|
||||
{loadData ? (
|
||||
<LoaderCustom />
|
||||
) : _.isEmpty(listData) ? (
|
||||
<TextCustom align="center" color="gray">
|
||||
Belum ada data
|
||||
</TextCustom>
|
||||
) : (
|
||||
listData?.map((item, index) => (
|
||||
<BaseBox key={index}>
|
||||
<StackCustom>
|
||||
<Grid>
|
||||
<Grid.Col span={8}>
|
||||
<TextCustom bold>{item?.title}</TextCustom>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={4} style={{ alignItems: "flex-end" }}>
|
||||
<TextCustom>{dayjs(item?.createdAt).format("DD MMM YYYY")}</TextCustom>
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
<TextCustom>{item?.deskripsi}</TextCustom>
|
||||
<ButtonCenteredOnly
|
||||
onPress={() => {
|
||||
router.navigate(`/(application)/(image)/preview-image/${item?.imageId}`);
|
||||
}}
|
||||
icon="file-text"
|
||||
>
|
||||
Bukti Transaksi
|
||||
</ButtonCenteredOnly>
|
||||
</StackCustom>
|
||||
</BaseBox>
|
||||
))
|
||||
)}
|
||||
</ViewWrapper>
|
||||
</>
|
||||
);
|
||||
|
||||
return <Donation_ScreenFundDisbursement donationId={id as string} />;
|
||||
}
|
||||
|
||||
@@ -6,10 +6,12 @@ import {
|
||||
DotButton,
|
||||
DrawerCustom,
|
||||
MenuDrawerDynamicGrid,
|
||||
NewWrapper,
|
||||
StackCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import { IconNews } from "@/components/_Icon";
|
||||
import CustomSkeleton from "@/components/_ShareComponent/SkeletonCustom";
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
import Donation_ComponentBoxDetailData from "@/screens/Donation/ComponentBoxDetailData";
|
||||
import Donation_ComponentInfoFundrising from "@/screens/Donation/ComponentInfoFundrising";
|
||||
@@ -34,7 +36,7 @@ export default function DonasiDetailBeranda() {
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
onLoadData();
|
||||
}, [id])
|
||||
}, [id]),
|
||||
);
|
||||
|
||||
const onLoadData = async () => {
|
||||
@@ -75,10 +77,10 @@ export default function DonasiDetailBeranda() {
|
||||
<>
|
||||
<BoxButtonOnFooter>
|
||||
<ButtonCustom
|
||||
disabled={value?.reminder}
|
||||
disabled={value?.reminder || !data}
|
||||
onPress={() => router.navigate(`/donation/${id}/(transaction-flow)`)}
|
||||
>
|
||||
{value?.reminder ? "Waktu berakhir" : "Donasi"}
|
||||
{!data ? "Loading..." : value?.reminder ? "Waktu berakhir" : "Donasi"}
|
||||
</ButtonCustom>
|
||||
</BoxButtonOnFooter>
|
||||
</>
|
||||
@@ -96,21 +98,30 @@ export default function DonasiDetailBeranda() {
|
||||
) : null,
|
||||
}}
|
||||
/>
|
||||
<ViewWrapper footerComponent={buttonSection}>
|
||||
<StackCustom>
|
||||
<Donation_ComponentBoxDetailData
|
||||
sisaHari={value.sisa}
|
||||
reminder={value.reminder}
|
||||
data={data}
|
||||
bottomSection={<Donation_ProgressSection id={id as string} progres={Number(data?.progres) || 0} />}
|
||||
/>
|
||||
<Donation_ComponentInfoFundrising dataAuthor={data?.Author} />
|
||||
<Donation_ComponentStoryFunrising
|
||||
id={id as string}
|
||||
dataStory={data?.CeritaDonasi}
|
||||
/>
|
||||
</StackCustom>
|
||||
</ViewWrapper>
|
||||
<NewWrapper footerComponent={buttonSection}>
|
||||
{!data ? (
|
||||
<CustomSkeleton height={400} />
|
||||
) : (
|
||||
<StackCustom>
|
||||
<Donation_ComponentBoxDetailData
|
||||
sisaHari={value.sisa}
|
||||
reminder={value.reminder}
|
||||
data={data}
|
||||
bottomSection={
|
||||
<Donation_ProgressSection
|
||||
id={id as string}
|
||||
progres={Number(data?.progres) || 0}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<Donation_ComponentInfoFundrising dataAuthor={data?.Author} />
|
||||
<Donation_ComponentStoryFunrising
|
||||
id={id as string}
|
||||
dataStory={data?.CeritaDonasi}
|
||||
/>
|
||||
</StackCustom>
|
||||
)}
|
||||
</NewWrapper>
|
||||
|
||||
<DrawerCustom
|
||||
isVisible={openDrawer}
|
||||
|
||||
@@ -1,94 +1,8 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
BaseBox,
|
||||
Grid,
|
||||
LoaderCustom,
|
||||
Spacing,
|
||||
StackCustom,
|
||||
TextCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
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 dayjs from "dayjs";
|
||||
import { useFocusEffect, useLocalSearchParams } from "expo-router";
|
||||
import _ from "lodash";
|
||||
import { useCallback, useState } from "react";
|
||||
import { useLocalSearchParams } from "expo-router";
|
||||
import Donation_ScreenListOfDonatur from "@/screens/Donation/ScreenListOfDonatur";
|
||||
|
||||
export default function Donation_ListOfDonatur() {
|
||||
export default function DonationListOfDonatur() {
|
||||
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 (
|
||||
<>
|
||||
<ViewWrapper>
|
||||
{loadData ? (
|
||||
<LoaderCustom />
|
||||
) : _.isEmpty(listData) ? (
|
||||
<TextCustom bold align="center">
|
||||
Belum ada donatur
|
||||
</TextCustom>
|
||||
) : (
|
||||
listData?.map((item: any, index: number) => (
|
||||
<BaseBox key={index}>
|
||||
<Grid>
|
||||
<Grid.Col
|
||||
span={3}
|
||||
style={{ alignItems: "center", justifyContent: "center" }}
|
||||
>
|
||||
<FontAwesome6
|
||||
name="face-smile-wink"
|
||||
size={50}
|
||||
style={{ color: MainColor.yellow }}
|
||||
/>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={9}>
|
||||
<TextCustom bold size="large">
|
||||
{item?.Author?.username || "-"}
|
||||
</TextCustom>
|
||||
<Spacing/>
|
||||
<StackCustom gap={"xs"}>
|
||||
<TextCustom size={"small"}>Berdonas sebesar </TextCustom>
|
||||
<TextCustom bold size="large" color="yellow">
|
||||
Rp. {formatCurrencyDisplay(item?.nominal)}
|
||||
</TextCustom>
|
||||
<TextCustom>
|
||||
{dayjs(item?.createdAt).format("DD MMM YYYY, HH:mm")}
|
||||
</TextCustom>
|
||||
</StackCustom>
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
</BaseBox>
|
||||
))
|
||||
)}
|
||||
</ViewWrapper>
|
||||
</>
|
||||
);
|
||||
|
||||
return <Donation_ScreenListOfDonatur donationId={id as string} />;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
BoxButtonOnFooter,
|
||||
ButtonCenteredOnly,
|
||||
ButtonCustom,
|
||||
InformationBox,
|
||||
@@ -8,8 +9,8 @@ import {
|
||||
StackCustom,
|
||||
TextAreaCustom,
|
||||
TextInputCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import NewWrapper from "@/components/_ShareComponent/NewWrapper";
|
||||
import DIRECTORY_ID from "@/constants/directory-id";
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
import {
|
||||
@@ -112,7 +113,23 @@ export default function DonationCreateStory() {
|
||||
};
|
||||
|
||||
return (
|
||||
<ViewWrapper>
|
||||
<NewWrapper
|
||||
hideFooter
|
||||
footerComponent={
|
||||
<>
|
||||
<BoxButtonOnFooter>
|
||||
<ButtonCustom
|
||||
isLoading={isLoading}
|
||||
onPress={() => {
|
||||
handlerSubmit();
|
||||
}}
|
||||
>
|
||||
Simpan
|
||||
</ButtonCustom>
|
||||
</BoxButtonOnFooter>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<StackCustom gap={"xs"}>
|
||||
<InformationBox text="Cerita Anda adalah kunci untuk menginspirasi kebaikan. Jelaskan dengan jujur dan jelas tujuan penggalangan dana ini agar calon donatur memahami dampak positif yang dapat mereka wujudkan melalui kontribusi mereka." />
|
||||
<TextAreaCustom
|
||||
@@ -166,18 +183,8 @@ export default function DonationCreateStory() {
|
||||
value={data.rekening}
|
||||
onChangeText={(value) => setData({ ...data, rekening: value })}
|
||||
/>
|
||||
|
||||
<Spacing />
|
||||
<ButtonCustom
|
||||
isLoading={isLoading}
|
||||
onPress={() => {
|
||||
handlerSubmit();
|
||||
}}
|
||||
>
|
||||
Simpan
|
||||
</ButtonCustom>
|
||||
</StackCustom>
|
||||
<Spacing />
|
||||
</ViewWrapper>
|
||||
</NewWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import {
|
||||
BoxButtonOnFooter,
|
||||
ButtonCenteredOnly,
|
||||
ButtonCustom,
|
||||
InformationBox,
|
||||
@@ -8,8 +9,8 @@ import {
|
||||
Spacing,
|
||||
StackCustom,
|
||||
TextInputCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import NewWrapper from "@/components/_ShareComponent/NewWrapper";
|
||||
import DIRECTORY_ID from "@/constants/directory-id";
|
||||
import { apiDonationCreate } from "@/service/api-client/api-donation";
|
||||
import { apiMasterDonation } from "@/service/api-client/api-master";
|
||||
@@ -43,7 +44,7 @@ export default function DonationCreate() {
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
onLoadList();
|
||||
}, [])
|
||||
}, []),
|
||||
);
|
||||
|
||||
const onLoadList = async () => {
|
||||
@@ -125,7 +126,24 @@ export default function DonationCreate() {
|
||||
};
|
||||
|
||||
return (
|
||||
<ViewWrapper>
|
||||
<NewWrapper
|
||||
hideFooter
|
||||
footerComponent={
|
||||
<>
|
||||
<BoxButtonOnFooter>
|
||||
<ButtonCustom
|
||||
isLoading={isLoading}
|
||||
onPress={() => {
|
||||
handlerSubmit();
|
||||
// router.push(`/donation/create-story?id=${"dasdsadsa"}`);
|
||||
}}
|
||||
>
|
||||
Selanjutnya
|
||||
</ButtonCustom>
|
||||
</BoxButtonOnFooter>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<StackCustom gap={"xs"}>
|
||||
<InformationBox text="Lengkapi semua data di bawah untuk selanjutnya mengisi cerita penggalangan dana." />
|
||||
|
||||
@@ -201,20 +219,8 @@ export default function DonationCreate() {
|
||||
onChange={(value: any) => setData({ ...data, durasiId: value })}
|
||||
/>
|
||||
)}
|
||||
|
||||
<Spacing />
|
||||
<ButtonCustom
|
||||
isLoading={isLoading}
|
||||
onPress={() => {
|
||||
handlerSubmit();
|
||||
// router.push(`/donation/create-story?id=${"dasdsadsa"}`);
|
||||
}}
|
||||
>
|
||||
Selanjutnya
|
||||
</ButtonCustom>
|
||||
<Spacing />
|
||||
</StackCustom>
|
||||
<Spacing />
|
||||
</ViewWrapper>
|
||||
</NewWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -58,7 +58,7 @@ export default function EventEdit() {
|
||||
try {
|
||||
setIsLoadData(true);
|
||||
const response = await apiEventGetOne({ id: id as string });
|
||||
console.log("[DATA BY ID]", JSON.stringify(response, null, 2));
|
||||
|
||||
if (response.success) {
|
||||
setData(response.data);
|
||||
setSelectedDate(new Date(response.data.tanggal));
|
||||
|
||||
@@ -29,14 +29,14 @@ export default function Application() {
|
||||
checkVersion();
|
||||
userData(token as string);
|
||||
syncUnreadCount();
|
||||
}, [user?.id, token])
|
||||
}, [user?.id, token]),
|
||||
);
|
||||
|
||||
async function onLoadData() {
|
||||
const response = await apiUser(user?.id as string);
|
||||
console.log(
|
||||
"[Profile ID]>>",
|
||||
JSON.stringify(response?.data?.Profile?.id, null, 2)
|
||||
JSON.stringify(response?.data?.Profile?.id, null, 2),
|
||||
);
|
||||
|
||||
setData(response.data);
|
||||
@@ -89,7 +89,12 @@ export default function Application() {
|
||||
/>
|
||||
<ViewWrapper
|
||||
refreshControl={
|
||||
<RefreshControl refreshing={refreshing} onRefresh={onRefresh} />
|
||||
<RefreshControl
|
||||
refreshing={refreshing}
|
||||
onRefresh={onRefresh}
|
||||
tintColor={MainColor.yellow}
|
||||
colors={[MainColor.yellow]}
|
||||
/>
|
||||
}
|
||||
footerComponent={
|
||||
<TabSection
|
||||
|
||||
@@ -1,56 +1,9 @@
|
||||
import {
|
||||
FloatingButton,
|
||||
LoaderCustom,
|
||||
ViewWrapper
|
||||
} from "@/components";
|
||||
import NoDataText from "@/components/_ShareComponent/NoDataText";
|
||||
import Investment_BoxBerandaSection from "@/screens/Invesment/BoxBerandaSection";
|
||||
import { apiInvestmentGetAll } from "@/service/api-client/api-investment";
|
||||
import { router, useFocusEffect } from "expo-router";
|
||||
import _ from "lodash";
|
||||
import { useCallback, useState } from "react";
|
||||
import Investment_ScreenBursa from "@/screens/Invesment/ScreenBursa";
|
||||
|
||||
export default function InvestmentBursa() {
|
||||
const [list, setList] = useState<any[] | null>(null);
|
||||
const [loadingList, setLoadingList] = useState(false);
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
onLoadList();
|
||||
}, [])
|
||||
);
|
||||
|
||||
const onLoadList = async () => {
|
||||
try {
|
||||
setLoadingList(true);
|
||||
const response = await apiInvestmentGetAll({
|
||||
category: "bursa"
|
||||
});
|
||||
// console.log("[DATA LIST]", JSON.stringify(response.data, null, 2));
|
||||
setList(response.data);
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
} finally {
|
||||
setLoadingList(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<ViewWrapper
|
||||
hideFooter
|
||||
floatingButton={
|
||||
<FloatingButton onPress={() => router.push("/investment/create")} />
|
||||
}
|
||||
>
|
||||
{loadingList ? (
|
||||
<LoaderCustom />
|
||||
) : _.isEmpty(list) ? (
|
||||
<NoDataText />
|
||||
) : (
|
||||
list?.map((item: any, index: number) => (
|
||||
<Investment_BoxBerandaSection id={item.id} data={item} key={index} />
|
||||
))
|
||||
)}
|
||||
</ViewWrapper>
|
||||
<>
|
||||
<Investment_ScreenBursa />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,83 +1,10 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
BaseBox,
|
||||
Grid,
|
||||
LoaderCustom,
|
||||
ProgressCustom,
|
||||
Spacing,
|
||||
StackCustom,
|
||||
TextCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import NoDataText from "@/components/_ShareComponent/NoDataText";
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
import { apiInvestmentGetAll } from "@/service/api-client/api-investment";
|
||||
import { formatCurrencyDisplay } from "@/utils/formatCurrencyDisplay";
|
||||
import { router, useFocusEffect } from "expo-router";
|
||||
import _ from "lodash";
|
||||
import React, { useCallback, useState } from "react";
|
||||
import { View } from "react-native";
|
||||
import Investment_ScreenMyHolding from "@/screens/Invesment/ScreenMyHolding";
|
||||
|
||||
export default function InvestmentMyHolding() {
|
||||
const { user } = useAuth();
|
||||
const [list, setList] = useState<any[] | null>(null);
|
||||
const [loadingList, setLoadingList] = useState(false);
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
onLoadList();
|
||||
}, [user?.id])
|
||||
);
|
||||
|
||||
const onLoadList = async () => {
|
||||
try {
|
||||
setLoadingList(true);
|
||||
const response = await apiInvestmentGetAll({
|
||||
category: "my-holding",
|
||||
authorId: user?.id,
|
||||
});
|
||||
console.log("[DATA LIST]", JSON.stringify(response.data, null, 2));
|
||||
setList(response.data);
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
} finally {
|
||||
setLoadingList(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<ViewWrapper hideFooter>
|
||||
{loadingList ? (
|
||||
<LoaderCustom />
|
||||
) : _.isEmpty(list) ? (
|
||||
<NoDataText />
|
||||
) : (
|
||||
list?.map((item, index) => (
|
||||
<BaseBox
|
||||
key={index}
|
||||
paddingTop={7}
|
||||
paddingBottom={7}
|
||||
onPress={() =>
|
||||
router.push(`/investment/${item?.id}/(my-holding)/${item?.id}`)
|
||||
}
|
||||
>
|
||||
<StackCustom>
|
||||
<TextCustom truncate={2}>{item?.title}</TextCustom>
|
||||
<TextCustom>
|
||||
Rp. {formatCurrencyDisplay(item?.nominal)}
|
||||
</TextCustom>
|
||||
<TextCustom>{item?.lembarTerbeli} Lembar</TextCustom>
|
||||
<ProgressCustom
|
||||
label={`${item.progress}%`}
|
||||
value={Number(item.progress)}
|
||||
size="lg"
|
||||
animated
|
||||
color="primary"
|
||||
/>
|
||||
</StackCustom>
|
||||
</BaseBox>
|
||||
))
|
||||
)}
|
||||
</ViewWrapper>
|
||||
<>
|
||||
<Investment_ScreenMyHolding />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,82 +1,10 @@
|
||||
/* 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 Investment_StatusBox from "@/screens/Invesment/StatusBox";
|
||||
import { apiInvestmentGetByStatus } from "@/service/api-client/api-investment";
|
||||
import { useFocusEffect, useLocalSearchParams } from "expo-router";
|
||||
import _ from "lodash";
|
||||
import { useCallback, useState } from "react";
|
||||
import Investment_ScreenPortofolio from "@/screens/Invesment/ScreenPortofolio";
|
||||
|
||||
export default function InvestmentPortofolio() {
|
||||
const { user } = useAuth();
|
||||
const { status } = useLocalSearchParams<{ status?: string }>();
|
||||
|
||||
const [activeCategory, setActiveCategory] = useState<string | null>(
|
||||
status || "publish"
|
||||
);
|
||||
|
||||
const [listData, setListData] = useState<any[]>([]);
|
||||
const [loadingList, setLoadingList] = useState(false);
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
onLoadData();
|
||||
}, [user?.id, activeCategory])
|
||||
);
|
||||
|
||||
const onLoadData = async () => {
|
||||
try {
|
||||
setLoadingList(true);
|
||||
const response = await apiInvestmentGetByStatus({
|
||||
authorId: user?.id as string,
|
||||
status: activeCategory as string,
|
||||
});
|
||||
setListData(response.data);
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
} finally {
|
||||
setLoadingList(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handlePress = (item: any) => {
|
||||
setActiveCategory(item.value);
|
||||
// tambahkan logika lain seperti filter dsb.
|
||||
};
|
||||
|
||||
const scrollComponent = (
|
||||
<ScrollableCustom
|
||||
data={dummyMasterStatus.map((e, i) => ({
|
||||
id: i,
|
||||
label: e.label,
|
||||
value: e.value,
|
||||
}))}
|
||||
onButtonPress={handlePress}
|
||||
activeId={activeCategory as any}
|
||||
/>
|
||||
);
|
||||
return (
|
||||
<ViewWrapper headerComponent={scrollComponent} hideFooter>
|
||||
{loadingList ? (
|
||||
<LoaderCustom />
|
||||
) : _.isEmpty(listData) ? (
|
||||
<TextCustom align="center">Tidak ada data {activeCategory}</TextCustom>
|
||||
) : (
|
||||
listData.map((item: any, index: number) => (
|
||||
<Investment_StatusBox
|
||||
key={index}
|
||||
data={item}
|
||||
status={activeCategory as string}
|
||||
href={`/investment/${item.id}/${activeCategory}/detail`}
|
||||
/>
|
||||
))
|
||||
)}
|
||||
</ViewWrapper>
|
||||
<>
|
||||
<Investment_ScreenPortofolio />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,124 +1,10 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
BadgeCustom,
|
||||
BaseBox,
|
||||
Grid,
|
||||
LoaderCustom,
|
||||
StackCustom,
|
||||
TextCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import NoDataText from "@/components/_ShareComponent/NoDataText";
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
import { apiInvestmentGetInvoice } from "@/service/api-client/api-investment";
|
||||
import { GStyles } from "@/styles/global-styles";
|
||||
import { formatChatTime } from "@/utils/formatChatTime";
|
||||
import { formatCurrencyDisplay } from "@/utils/formatCurrencyDisplay";
|
||||
import { router, useFocusEffect } from "expo-router";
|
||||
import _ from "lodash";
|
||||
import { useCallback, useState } from "react";
|
||||
import { View } from "react-native";
|
||||
import Investment_ScreenTransaction from "@/screens/Invesment/ScreenTransaction";
|
||||
|
||||
export default function InvestmentTransaction() {
|
||||
const { user } = useAuth();
|
||||
const [list, setList] = useState<any>([]);
|
||||
const [loadList, setLoadList] = useState<boolean>(false);
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
onLoadList();
|
||||
}, [user?.id])
|
||||
);
|
||||
|
||||
const onLoadList = async () => {
|
||||
try {
|
||||
setLoadList(true);
|
||||
const response = await apiInvestmentGetInvoice({
|
||||
authorId: user?.id as string,
|
||||
category: "transaction",
|
||||
});
|
||||
console.log("[RESPONSE LIST]", JSON.stringify(response.data, null, 2));
|
||||
setList(response.data);
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
} finally {
|
||||
setLoadList(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handlerColor = (status: string) => {
|
||||
if (status === "menunggu") {
|
||||
return "orange";
|
||||
} else if (status === "proses") {
|
||||
return "white";
|
||||
} else if (status === "berhasil") {
|
||||
return "green";
|
||||
} else if (status === "gagal") {
|
||||
return "red";
|
||||
}
|
||||
};
|
||||
|
||||
const handlePress = ({ id, status }: { id: string; status: string }) => {
|
||||
if (status === "menunggu") {
|
||||
router.push(`/investment/${id}/(transaction-flow)/invoice`);
|
||||
} else if (status === "proses") {
|
||||
router.push(`/investment/${id}/(transaction-flow)/process`);
|
||||
} else if (status === "berhasil") {
|
||||
router.push(`/investment/${id}/(transaction-flow)/success`);
|
||||
} else if (status === "gagal") {
|
||||
router.push(`/investment/${id}/(transaction-flow)/failed`);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<ViewWrapper hideFooter>
|
||||
{loadList ? (
|
||||
<LoaderCustom />
|
||||
) : _.isEmpty(list) ? (
|
||||
<NoDataText/>
|
||||
) : (
|
||||
list.map((item: any, i: number) => (
|
||||
<BaseBox
|
||||
key={i}
|
||||
paddingTop={7}
|
||||
paddingBottom={7}
|
||||
onPress={() => {
|
||||
handlePress({
|
||||
id: item.id,
|
||||
status: _.lowerCase(item.statusInvoice),
|
||||
});
|
||||
}}
|
||||
>
|
||||
<Grid>
|
||||
<Grid.Col span={6}>
|
||||
<StackCustom gap={"xs"}>
|
||||
<TextCustom truncate>{item?.title || "-"}</TextCustom>
|
||||
<TextCustom color="gray" size="small">
|
||||
{formatChatTime(item?.createdAt)}
|
||||
</TextCustom>
|
||||
</StackCustom>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={1}>
|
||||
<View />
|
||||
</Grid.Col>
|
||||
<Grid.Col span={5} style={{ alignItems: "flex-end" }}>
|
||||
<StackCustom gap={"xs"}>
|
||||
<TextCustom bold truncate>
|
||||
Rp. {formatCurrencyDisplay(item?.nominal) || "-"}
|
||||
</TextCustom>
|
||||
<BadgeCustom
|
||||
variant="light"
|
||||
color={handlerColor(_.lowerCase(item.statusInvoice))}
|
||||
style={GStyles.alignSelfFlexEnd}
|
||||
>
|
||||
{item?.statusInvoice || "-"}
|
||||
</BadgeCustom>
|
||||
</StackCustom>
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
</BaseBox>
|
||||
))
|
||||
)}
|
||||
</ViewWrapper>
|
||||
<>
|
||||
<Investment_ScreenTransaction />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,58 +1,10 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import { LoaderCustom, TextCustom, ViewWrapper } from "@/components";
|
||||
import Investment_BoxDetailDocument from "@/screens/Invesment/Document/RecapBoxDetail";
|
||||
import { apiInvestmentGetDocument } from "@/service/api-client/api-investment";
|
||||
import { useFocusEffect, useLocalSearchParams } from "expo-router";
|
||||
import _ from "lodash";
|
||||
import { useCallback, useState } from "react";
|
||||
import Investment_ScreenListOfDocument from "@/screens/Invesment/Document/ScreenListDocument";
|
||||
|
||||
export default function InvestmentListOfDocument() {
|
||||
const { id } = useLocalSearchParams();
|
||||
console.log("ID >> ", id);
|
||||
|
||||
const [list, setList] = useState<any[] | null>(null);
|
||||
const [loadList, setLoadList] = useState(false);
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
onLoadListDocument();
|
||||
}, [id])
|
||||
);
|
||||
|
||||
const onLoadListDocument = async () => {
|
||||
try {
|
||||
setLoadList(true);
|
||||
const response = await apiInvestmentGetDocument({
|
||||
id: id as string,
|
||||
category: "all-document",
|
||||
});
|
||||
|
||||
setList(response.data);
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
setList([]);
|
||||
} finally {
|
||||
setLoadList(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<ViewWrapper>
|
||||
{loadList ? (
|
||||
<LoaderCustom />
|
||||
) : _.isEmpty(list) ? (
|
||||
<TextCustom align="center" color="gray">
|
||||
Tidak ada data
|
||||
</TextCustom>
|
||||
) : (
|
||||
list?.map((item: any, index: number) => (
|
||||
<Investment_BoxDetailDocument
|
||||
key={index}
|
||||
title={item.title}
|
||||
href={`/(file)/${item.fileId}`}
|
||||
/>
|
||||
))
|
||||
)}
|
||||
</ViewWrapper>
|
||||
<>
|
||||
<Investment_ScreenListOfDocument />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,213 +1,10 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
AlertDefaultSystem,
|
||||
BackButton,
|
||||
DotButton,
|
||||
DrawerCustom,
|
||||
LoaderCustom,
|
||||
MenuDrawerDynamicGrid,
|
||||
TextCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import { IconEdit } from "@/components/_Icon";
|
||||
import { MainColor } from "@/constants/color-palet";
|
||||
import { ICON_SIZE_SMALL } from "@/constants/constans-value";
|
||||
import Investment_BoxDetailDocument from "@/screens/Invesment/Document/RecapBoxDetail";
|
||||
import {
|
||||
apiInvestmentDeleteDocument,
|
||||
apiInvestmentGetDocument,
|
||||
} from "@/service/api-client/api-investment";
|
||||
import { AntDesign, Ionicons } from "@expo/vector-icons";
|
||||
import {
|
||||
router,
|
||||
Stack,
|
||||
useFocusEffect,
|
||||
useLocalSearchParams,
|
||||
} from "expo-router";
|
||||
import _ from "lodash";
|
||||
import { useCallback, useState } from "react";
|
||||
import Toast from "react-native-toast-message";
|
||||
import Investment_ScreenRecapOfDocument from "@/screens/Invesment/Document/ScreenRecapOfDocument";
|
||||
|
||||
export default function InvestmentRecapOfDocument() {
|
||||
const { id } = useLocalSearchParams();
|
||||
const [openDrawer, setOpenDrawer] = useState(false);
|
||||
const [openDrawerBox, setOpenDrawerBox] = useState(false);
|
||||
const [list, setList] = useState<any[] | null>(null);
|
||||
const [loadList, setLoadList] = useState(false);
|
||||
const [selectId, setSelectId] = useState<string | null>(null);
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
onLoadListDocument();
|
||||
}, [id])
|
||||
);
|
||||
|
||||
const onLoadListDocument = async () => {
|
||||
try {
|
||||
setLoadList(true);
|
||||
const response = await apiInvestmentGetDocument({
|
||||
id: id as string,
|
||||
category: "all-document",
|
||||
});
|
||||
|
||||
setList(response.data);
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
setList([]);
|
||||
} finally {
|
||||
setLoadList(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handlerDeleteDocument = async () => {
|
||||
try {
|
||||
const response = await apiInvestmentDeleteDocument({
|
||||
id: selectId as string,
|
||||
});
|
||||
|
||||
if (response.success) {
|
||||
Toast.show({
|
||||
type: "success",
|
||||
text1: "Data berhasil dihapus",
|
||||
});
|
||||
setList((prev: any[] | null) => {
|
||||
if (!prev) return null;
|
||||
return prev.filter((item: any) => item.id !== selectId);
|
||||
});
|
||||
setOpenDrawerBox(false);
|
||||
setSelectId(null);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
Toast.show({
|
||||
type: "error",
|
||||
text1: "Gagal menghapus data",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Stack.Screen
|
||||
options={{
|
||||
title: "Rekap Dokumen",
|
||||
headerLeft: () => <BackButton />,
|
||||
headerRight: () => (
|
||||
<DotButton
|
||||
onPress={() => {
|
||||
setOpenDrawer(true);
|
||||
setOpenDrawerBox(false);
|
||||
}}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
|
||||
<ViewWrapper>
|
||||
{loadList ? (
|
||||
<LoaderCustom />
|
||||
) : _.isEmpty(list) ? (
|
||||
<TextCustom align="center" color="gray">
|
||||
Tidak ada data
|
||||
</TextCustom>
|
||||
) : (
|
||||
list?.map((item: any, index: number) => (
|
||||
<Investment_BoxDetailDocument
|
||||
key={index}
|
||||
title={item.title}
|
||||
leftIcon={
|
||||
<Ionicons
|
||||
name="ellipsis-horizontal-outline"
|
||||
size={ICON_SIZE_SMALL}
|
||||
color={MainColor.white}
|
||||
style={{
|
||||
zIndex: 10,
|
||||
alignSelf: "flex-end",
|
||||
}}
|
||||
onPress={() => {
|
||||
setSelectId(item.id);
|
||||
setOpenDrawerBox(true);
|
||||
}}
|
||||
/>
|
||||
}
|
||||
href={`/(file)/${item.fileId}`}
|
||||
/>
|
||||
))
|
||||
)}
|
||||
</ViewWrapper>
|
||||
|
||||
{/* Drawer On Header */}
|
||||
<DrawerCustom
|
||||
isVisible={openDrawer}
|
||||
closeDrawer={() => setOpenDrawer(false)}
|
||||
height={"auto"}
|
||||
>
|
||||
<MenuDrawerDynamicGrid
|
||||
data={[
|
||||
{
|
||||
icon: (
|
||||
<AntDesign
|
||||
name="plus-circle"
|
||||
size={ICON_SIZE_SMALL}
|
||||
color={MainColor.white}
|
||||
/>
|
||||
),
|
||||
label: "Tambah Dokumen",
|
||||
path: `/investment/${id}/(document)/add-document`,
|
||||
},
|
||||
]}
|
||||
onPressItem={(item) => {
|
||||
router.push(item.path as any);
|
||||
setOpenDrawer(false);
|
||||
}}
|
||||
/>
|
||||
</DrawerCustom>
|
||||
|
||||
{/* Drawer On Box */}
|
||||
<DrawerCustom
|
||||
isVisible={openDrawerBox}
|
||||
closeDrawer={() => setOpenDrawerBox(false)}
|
||||
height={"auto"}
|
||||
>
|
||||
<MenuDrawerDynamicGrid
|
||||
data={[
|
||||
{
|
||||
icon: <IconEdit />,
|
||||
label: "Edit Dokumen",
|
||||
path: `/investment/${selectId}/(document)/edit-document`,
|
||||
},
|
||||
{
|
||||
icon: (
|
||||
<Ionicons
|
||||
name="trash-outline"
|
||||
size={ICON_SIZE_SMALL}
|
||||
color={MainColor.white}
|
||||
/>
|
||||
),
|
||||
label: "Hapus Dokumen",
|
||||
path: "" as any,
|
||||
color: MainColor.red,
|
||||
},
|
||||
]}
|
||||
onPressItem={(item) => {
|
||||
if (item.path === ("" as any)) {
|
||||
AlertDefaultSystem({
|
||||
title: "Hapus Dokumen",
|
||||
message: "Apakah anda yakin ingin menghapus dokumen ini?",
|
||||
textLeft: "Batal",
|
||||
textRight: "Hapus",
|
||||
onPressRight: () => {
|
||||
handlerDeleteDocument();
|
||||
},
|
||||
});
|
||||
} else {
|
||||
router.push(item.path as any);
|
||||
}
|
||||
|
||||
setOpenDrawerBox(false);
|
||||
}}
|
||||
/>
|
||||
</DrawerCustom>
|
||||
<Investment_ScreenRecapOfDocument />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,100 +1,10 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
BackButton,
|
||||
BaseBox,
|
||||
DrawerCustom,
|
||||
LoaderCustom,
|
||||
MenuDrawerDynamicGrid,
|
||||
TextCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import { IconPlus } from "@/components/_Icon";
|
||||
import { apiInvestmentGetNews } from "@/service/api-client/api-investment";
|
||||
import {
|
||||
router,
|
||||
Stack,
|
||||
useFocusEffect,
|
||||
useLocalSearchParams,
|
||||
} from "expo-router";
|
||||
import _ from "lodash";
|
||||
import { useCallback, useState } from "react";
|
||||
import Investment_ScreenListOfNews from "@/screens/Invesment/News/ScreenListOfNews";
|
||||
import { useLocalSearchParams } from "expo-router";
|
||||
|
||||
export default function InvestmentListOfNews() {
|
||||
const { id } = useLocalSearchParams();
|
||||
const [openDrawer, setOpenDrawer] = useState(false);
|
||||
const [list, setList] = useState<any[] | null>(null);
|
||||
const [loadList, setLoadList] = useState(false);
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
onLoadList();
|
||||
}, [id])
|
||||
);
|
||||
|
||||
const onLoadList = async () => {
|
||||
try {
|
||||
setLoadList(true);
|
||||
const response = await apiInvestmentGetNews({
|
||||
id: id as string,
|
||||
category: "all-news",
|
||||
});
|
||||
|
||||
setList(response.data);
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
} finally {
|
||||
setLoadList(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Stack.Screen
|
||||
options={{
|
||||
title: "Daftar Berita",
|
||||
headerLeft: () => <BackButton />,
|
||||
// headerRight: () => <DotButton onPress={() => setOpenDrawer(true)} />,
|
||||
}}
|
||||
/>
|
||||
|
||||
<ViewWrapper>
|
||||
{loadList ? (
|
||||
<LoaderCustom />
|
||||
) : _.isEmpty(list) ? (
|
||||
<TextCustom align="center" color="gray">
|
||||
Tidak ada data
|
||||
</TextCustom>
|
||||
) : (
|
||||
list?.map((item: any, index: number) => (
|
||||
<BaseBox
|
||||
key={index}
|
||||
paddingBlock={5}
|
||||
href={`/investment/[id]/(news)/${item.id}`}
|
||||
>
|
||||
<TextCustom bold>{item.title}</TextCustom>
|
||||
</BaseBox>
|
||||
))
|
||||
)}
|
||||
</ViewWrapper>
|
||||
|
||||
<DrawerCustom
|
||||
isVisible={openDrawer}
|
||||
closeDrawer={() => setOpenDrawer(false)}
|
||||
height={"auto"}
|
||||
>
|
||||
<MenuDrawerDynamicGrid
|
||||
data={[
|
||||
{
|
||||
label: "Tambah Berita",
|
||||
path: `/investment/${id}/add-news`,
|
||||
icon: <IconPlus />,
|
||||
},
|
||||
]}
|
||||
onPressItem={(item) => {
|
||||
router.push(item.path as any);
|
||||
setOpenDrawer(false);
|
||||
}}
|
||||
/>
|
||||
</DrawerCustom>
|
||||
</>
|
||||
<Investment_ScreenListOfNews investmentId={id as string} />
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,101 +1,10 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
BackButton,
|
||||
BaseBox,
|
||||
DotButton,
|
||||
DrawerCustom,
|
||||
LoaderCustom,
|
||||
MenuDrawerDynamicGrid,
|
||||
TextCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import { IconPlus } from "@/components/_Icon";
|
||||
import { apiInvestmentGetNews } from "@/service/api-client/api-investment";
|
||||
import {
|
||||
router,
|
||||
Stack,
|
||||
useFocusEffect,
|
||||
useLocalSearchParams,
|
||||
} from "expo-router";
|
||||
import _ from "lodash";
|
||||
import { useCallback, useState } from "react";
|
||||
import Investment_ScreenRecapOfNews from "@/screens/Invesment/News/ScreenRecapOfNews";
|
||||
import { useLocalSearchParams } from "expo-router";
|
||||
|
||||
export default function InvestmentRecapOfNews() {
|
||||
const { id } = useLocalSearchParams();
|
||||
const [openDrawer, setOpenDrawer] = useState(false);
|
||||
const [list, setList] = useState<any[] | null>(null);
|
||||
const [loadList, setLoadList] = useState(false);
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
onLoadList();
|
||||
}, [id])
|
||||
);
|
||||
|
||||
const onLoadList = async () => {
|
||||
try {
|
||||
setLoadList(true);
|
||||
const response = await apiInvestmentGetNews({
|
||||
id: id as string,
|
||||
category: "all-news",
|
||||
});
|
||||
|
||||
setList(response.data);
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
} finally {
|
||||
setLoadList(false);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
<Stack.Screen
|
||||
options={{
|
||||
title: "Rekap Berita",
|
||||
headerLeft: () => <BackButton />,
|
||||
headerRight: () => <DotButton onPress={() => setOpenDrawer(true)} />,
|
||||
}}
|
||||
/>
|
||||
<ViewWrapper>
|
||||
{loadList ? (
|
||||
<LoaderCustom />
|
||||
) : _.isEmpty(list) ? (
|
||||
<TextCustom align="center" color="gray">
|
||||
Tidak ada data
|
||||
</TextCustom>
|
||||
) : (
|
||||
list?.map((item: any, index: number) => (
|
||||
<BaseBox
|
||||
key={index}
|
||||
paddingBlock={5}
|
||||
href={`/investment/[id]/(news)/${item.id}`}
|
||||
>
|
||||
<TextCustom bold>{item.title}</TextCustom>
|
||||
</BaseBox>
|
||||
))
|
||||
)}
|
||||
</ViewWrapper>
|
||||
|
||||
<DrawerCustom
|
||||
isVisible={openDrawer}
|
||||
closeDrawer={() => setOpenDrawer(false)}
|
||||
height={"auto"}
|
||||
>
|
||||
<MenuDrawerDynamicGrid
|
||||
data={[
|
||||
{
|
||||
label: "Tambah Berita",
|
||||
path: `/investment/${id}/add-news`,
|
||||
icon: <IconPlus />,
|
||||
},
|
||||
]}
|
||||
onPressItem={(item) => {
|
||||
router.push(item.path as any);
|
||||
setOpenDrawer(false);
|
||||
}}
|
||||
/>
|
||||
</DrawerCustom>
|
||||
</>
|
||||
<Investment_ScreenRecapOfNews investmentId={id as string} />
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,230 +1,10 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
BaseBox,
|
||||
ButtonCenteredOnly,
|
||||
ButtonCustom,
|
||||
Grid,
|
||||
InformationBox,
|
||||
Spacing,
|
||||
StackCustom,
|
||||
TextCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import CopyButton from "@/components/Button/CoyButton";
|
||||
import { MainColor } from "@/constants/color-palet";
|
||||
import DIRECTORY_ID from "@/constants/directory-id";
|
||||
import {
|
||||
apiInvestmentGetInvoice,
|
||||
apiInvestmentUpdateInvoice,
|
||||
} from "@/service/api-client/api-investment";
|
||||
import { uploadFileService } from "@/service/upload-service";
|
||||
import { formatCurrencyDisplay } from "@/utils/formatCurrencyDisplay";
|
||||
import pickFile, { IFileData } from "@/utils/pickFile";
|
||||
import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
|
||||
import { useCallback, useState } from "react";
|
||||
import { View } from "react-native";
|
||||
import Toast from "react-native-toast-message";
|
||||
import Investment_ScreenInvoice from "@/screens/Invesment/ScreenInvoice";
|
||||
|
||||
export default function InvestmentInvoice() {
|
||||
const { id } = useLocalSearchParams();
|
||||
const [data, setData] = useState<any>({});
|
||||
const [image, setImage] = useState<IFileData>({
|
||||
name: "",
|
||||
uri: "",
|
||||
size: 0,
|
||||
});
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
onLoadData();
|
||||
}, [id])
|
||||
);
|
||||
|
||||
const onLoadData = async () => {
|
||||
try {
|
||||
const response = await apiInvestmentGetInvoice({
|
||||
id: id as string,
|
||||
category: "invoice",
|
||||
});
|
||||
|
||||
setData(response.data);
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
}
|
||||
};
|
||||
|
||||
const handlerSubmitUpdate = async () => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
const responseUploadImage = await uploadFileService({
|
||||
dirId: DIRECTORY_ID.investasi_bukti_transfer,
|
||||
imageUri: image?.uri,
|
||||
});
|
||||
|
||||
if (!responseUploadImage?.data?.id) {
|
||||
Toast.show({
|
||||
type: "error",
|
||||
text1: "Gagal mengunggah bukti transfer",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const response = await apiInvestmentUpdateInvoice({
|
||||
id: id as string,
|
||||
data: {
|
||||
imageId: responseUploadImage?.data?.id,
|
||||
},
|
||||
status: "proses",
|
||||
});
|
||||
|
||||
if (response.success) {
|
||||
Toast.show({
|
||||
type: "success",
|
||||
text1: "Berhasil mengunggah bukti transfer",
|
||||
});
|
||||
router.push(`/investment/${id}/(transaction-flow)/process`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<ViewWrapper>
|
||||
<StackCustom>
|
||||
<InformationBox text="Mohon transfer ke rekening dibawah" />
|
||||
<BaseBox>
|
||||
<StackCustom gap={"xs"}>
|
||||
<Grid>
|
||||
<Grid.Col span={4}>
|
||||
<TextCustom>Bank</TextCustom>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={8}>
|
||||
<TextCustom>{data?.MasterBank?.namaBank}</TextCustom>
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
<Spacing height={10} />
|
||||
<Grid>
|
||||
<Grid.Col span={4}>
|
||||
<TextCustom>Nama Akun</TextCustom>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={8}>
|
||||
<TextCustom>{data?.MasterBank?.namaAkun}</TextCustom>
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
|
||||
<BaseBox backgroundColor={MainColor.soft_darkblue}>
|
||||
<Grid containerStyle={{ justifyContent: "center" }}>
|
||||
<Grid.Col
|
||||
span={8}
|
||||
style={{
|
||||
justifyContent: "center",
|
||||
}}
|
||||
>
|
||||
<TextCustom size="xlarge" bold color="yellow">
|
||||
{data?.MasterBank?.norek}
|
||||
</TextCustom>
|
||||
</Grid.Col>
|
||||
<Grid.Col
|
||||
span={4}
|
||||
style={{
|
||||
alignItems: "flex-end",
|
||||
}}
|
||||
>
|
||||
<CopyButton textToCopy={data?.MasterBank?.norek} />
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
</BaseBox>
|
||||
</StackCustom>
|
||||
</BaseBox>
|
||||
|
||||
<BaseBox>
|
||||
<StackCustom gap={"xs"}>
|
||||
<TextCustom>Jumlah Transaksi</TextCustom>
|
||||
|
||||
<Spacing height={10} />
|
||||
|
||||
<BaseBox backgroundColor={MainColor.soft_darkblue}>
|
||||
<Grid containerStyle={{ justifyContent: "center" }}>
|
||||
<Grid.Col
|
||||
span={8}
|
||||
style={{
|
||||
justifyContent: "center",
|
||||
}}
|
||||
>
|
||||
<TextCustom size="xlarge" bold color="yellow">
|
||||
Rp. {formatCurrencyDisplay(data?.nominal)}
|
||||
</TextCustom>
|
||||
</Grid.Col>
|
||||
<Grid.Col
|
||||
span={4}
|
||||
style={{
|
||||
alignItems: "flex-end",
|
||||
}}
|
||||
>
|
||||
<CopyButton textToCopy={data?.nominal} />
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
</BaseBox>
|
||||
</StackCustom>
|
||||
</BaseBox>
|
||||
|
||||
<BaseBox>
|
||||
<StackCustom>
|
||||
<TextCustom align="center">
|
||||
Upload bukti transfer anda.
|
||||
</TextCustom>
|
||||
{image ? (
|
||||
<View
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
gap: 10,
|
||||
paddingInline: 20,
|
||||
}}
|
||||
>
|
||||
<TextCustom bold align="center" truncate>
|
||||
{image?.name}
|
||||
</TextCustom>
|
||||
</View>
|
||||
) : (
|
||||
<TextCustom align="center">
|
||||
Tidak ada gambar yang diunggah
|
||||
</TextCustom>
|
||||
)}
|
||||
<ButtonCenteredOnly
|
||||
onPress={() => {
|
||||
pickFile({
|
||||
allowedType: "image",
|
||||
setImageUri(file: any) {
|
||||
setImage(file);
|
||||
},
|
||||
});
|
||||
}}
|
||||
icon="upload"
|
||||
>
|
||||
Upload
|
||||
</ButtonCenteredOnly>
|
||||
</StackCustom>
|
||||
</BaseBox>
|
||||
|
||||
<ButtonCustom
|
||||
isLoading={isLoading}
|
||||
disabled={!image || isLoading}
|
||||
onPress={() => {
|
||||
handlerSubmitUpdate();
|
||||
}}
|
||||
>
|
||||
Saya Sudah Transfer
|
||||
</ButtonCustom>
|
||||
</StackCustom>
|
||||
<Spacing />
|
||||
</ViewWrapper>
|
||||
<Investment_ScreenInvoice />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -56,7 +56,6 @@ export default function InvestmentSelectBank() {
|
||||
});
|
||||
|
||||
if (response.success) {
|
||||
console.log("[RESPONSE >>]", response);
|
||||
const invoiceId = response.data.id;
|
||||
|
||||
const delStorage = await AsyncStorage.removeItem(
|
||||
|
||||
@@ -73,7 +73,6 @@ export default function InvestmentDetailStatus() {
|
||||
updateCountDown();
|
||||
}, [data]);
|
||||
|
||||
console.log("[DATA DETAIL]", JSON.stringify(data, null, 2));
|
||||
|
||||
const updateCountDown = () => {
|
||||
const countDown = countDownAndCondition({
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
BoxButtonOnFooter,
|
||||
ButtonCenteredOnly,
|
||||
ButtonCustom,
|
||||
InformationBox,
|
||||
LandscapeFrameUploaded,
|
||||
LoaderCustom,
|
||||
NewWrapper,
|
||||
SelectCustom,
|
||||
Spacing,
|
||||
StackCustom,
|
||||
TextInputCustom,
|
||||
ViewWrapper,
|
||||
TextInputCustom
|
||||
} from "@/components";
|
||||
import API_STRORAGE from "@/constants/base-url-api-strorage";
|
||||
import DIRECTORY_ID from "@/constants/directory-id";
|
||||
@@ -198,7 +199,15 @@ export default function InvestmentEdit() {
|
||||
};
|
||||
|
||||
return (
|
||||
<ViewWrapper>
|
||||
<NewWrapper
|
||||
footerComponent={
|
||||
<BoxButtonOnFooter>
|
||||
<ButtonCustom isLoading={isLoading} onPress={handleSubmitUpdate}>
|
||||
Simpan
|
||||
</ButtonCustom>
|
||||
</BoxButtonOnFooter>
|
||||
}
|
||||
>
|
||||
<StackCustom gap={"xs"}>
|
||||
<InformationBox text="Gambar investasi bisa berupa ilustrasi, poster atau foto terkait investasi." />
|
||||
<LandscapeFrameUploaded
|
||||
@@ -253,7 +262,8 @@ export default function InvestmentEdit() {
|
||||
/>
|
||||
|
||||
<TextInputCustom
|
||||
disabled
|
||||
iconLeft="Rp."
|
||||
// disabled
|
||||
required
|
||||
placeholder="0"
|
||||
label="Total Lembar"
|
||||
@@ -339,11 +349,7 @@ export default function InvestmentEdit() {
|
||||
)}
|
||||
|
||||
<Spacing />
|
||||
<ButtonCustom isLoading={isLoading} onPress={handleSubmitUpdate}>
|
||||
Simpan
|
||||
</ButtonCustom>
|
||||
</StackCustom>
|
||||
<Spacing height={50} />
|
||||
</ViewWrapper>
|
||||
</NewWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -72,7 +72,6 @@ export default function InvestmentDetail() {
|
||||
updateCountDown();
|
||||
}, [data]);
|
||||
|
||||
console.log("[DATA DETAIL]", JSON.stringify(data, null, 2));
|
||||
|
||||
const updateCountDown = () => {
|
||||
const countDown = countDownAndCondition({
|
||||
|
||||
@@ -1,67 +1,10 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
AvatarUsernameAndOtherComponent,
|
||||
BoxWithHeaderSection,
|
||||
LoaderCustom,
|
||||
TextCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import NoDataText from "@/components/_ShareComponent/NoDataText";
|
||||
import { apiInvestmentGetInvestorById } from "@/service/api-client/api-investment";
|
||||
import { formatCurrencyDisplay } from "@/utils/formatCurrencyDisplay";
|
||||
import { useFocusEffect, useLocalSearchParams } from "expo-router";
|
||||
import _ from "lodash";
|
||||
import { useCallback, useState } from "react";
|
||||
import Investment_ScreenInvestor from "@/screens/Invesment/ScreenInvestor";
|
||||
import { useLocalSearchParams } from "expo-router";
|
||||
|
||||
export default function InvestmentInvestor() {
|
||||
const { id } = useLocalSearchParams();
|
||||
const [list, setList] = useState<any[] | null>(null);
|
||||
const [loadingList, setLoadingList] = useState(false);
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
onLoadList();
|
||||
}, [id])
|
||||
);
|
||||
|
||||
const onLoadList = async () => {
|
||||
try {
|
||||
setLoadingList(true);
|
||||
const response = await apiInvestmentGetInvestorById({
|
||||
id: id as string,
|
||||
})
|
||||
console.log("[DATA LIST]", JSON.stringify(response.data, null, 2));
|
||||
setList(response.data);
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
} finally {
|
||||
setLoadingList(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
<ViewWrapper>
|
||||
{loadingList ? (
|
||||
<LoaderCustom />
|
||||
) : _.isEmpty(list) ? (
|
||||
<NoDataText />
|
||||
) : (
|
||||
list?.map((item: any, index: number) => (
|
||||
<BoxWithHeaderSection key={index}>
|
||||
<AvatarUsernameAndOtherComponent
|
||||
avatar={item?.Author?.Profile?.imageId}
|
||||
name={item?.Author?.username}
|
||||
avatarHref={`/profile/${item?.Author?.Profile?.id}`}
|
||||
/>
|
||||
<TextCustom bold>
|
||||
Rp. {formatCurrencyDisplay(item?.nominal)}
|
||||
</TextCustom>
|
||||
</BoxWithHeaderSection>
|
||||
))
|
||||
)}
|
||||
</ViewWrapper>
|
||||
</>
|
||||
<Investment_ScreenInvestor investmentId={id as string} />
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,18 +1,19 @@
|
||||
import {
|
||||
BaseBox,
|
||||
BoxButtonOnFooter,
|
||||
ButtonCenteredOnly,
|
||||
ButtonCustom,
|
||||
CenterCustom,
|
||||
InformationBox,
|
||||
LandscapeFrameUploaded,
|
||||
LoaderCustom,
|
||||
NewWrapper,
|
||||
SelectCustom,
|
||||
Spacing,
|
||||
StackCustom,
|
||||
TextCustom,
|
||||
TextInputCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import CustomSkeleton from "@/components/_ShareComponent/SkeletonCustom";
|
||||
import { MainColor } from "@/constants/color-palet";
|
||||
import DIRECTORY_ID from "@/constants/directory-id";
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
@@ -184,7 +185,19 @@ export default function InvestmentCreate() {
|
||||
|
||||
// const [coba, setCoba] = useState("");
|
||||
return (
|
||||
<ViewWrapper>
|
||||
<NewWrapper
|
||||
footerComponent={
|
||||
<BoxButtonOnFooter>
|
||||
<ButtonCustom
|
||||
disabled={isLoading}
|
||||
isLoading={isLoading}
|
||||
onPress={() => handleSubmit()}
|
||||
>
|
||||
Simpan
|
||||
</ButtonCustom>
|
||||
</BoxButtonOnFooter>
|
||||
}
|
||||
>
|
||||
<StackCustom gap={"xs"}>
|
||||
<InformationBox text="Gambar investasi bisa berupa ilustrasi, poster atau foto terkait investasi." />
|
||||
<LandscapeFrameUploaded image={image as string} />
|
||||
@@ -264,7 +277,9 @@ export default function InvestmentCreate() {
|
||||
|
||||
<StackCustom gap={0}>
|
||||
<TextInputCustom
|
||||
disabled
|
||||
iconLeft="Rp."
|
||||
// disabled
|
||||
editable={false}
|
||||
required
|
||||
placeholder="0"
|
||||
label="Total Lembar"
|
||||
@@ -291,7 +306,7 @@ export default function InvestmentCreate() {
|
||||
/>
|
||||
|
||||
{loadingMaster ? (
|
||||
<LoaderCustom />
|
||||
<CustomSkeleton height={50} />
|
||||
) : (
|
||||
<SelectCustom
|
||||
required
|
||||
@@ -313,7 +328,7 @@ export default function InvestmentCreate() {
|
||||
)}
|
||||
|
||||
{loadingMaster ? (
|
||||
<LoaderCustom />
|
||||
<CustomSkeleton height={50} />
|
||||
) : (
|
||||
<SelectCustom
|
||||
required
|
||||
@@ -335,7 +350,7 @@ export default function InvestmentCreate() {
|
||||
)}
|
||||
|
||||
{loadingMaster ? (
|
||||
<LoaderCustom />
|
||||
<CustomSkeleton height={50} />
|
||||
) : (
|
||||
<SelectCustom
|
||||
required
|
||||
@@ -357,15 +372,8 @@ export default function InvestmentCreate() {
|
||||
)}
|
||||
|
||||
<Spacing />
|
||||
<ButtonCustom
|
||||
disabled={isLoading}
|
||||
isLoading={isLoading}
|
||||
onPress={() => handleSubmit()}
|
||||
>
|
||||
Simpan
|
||||
</ButtonCustom>
|
||||
</StackCustom>
|
||||
<Spacing height={50} />
|
||||
</ViewWrapper>
|
||||
{/* <Spacing height={50} /> */}
|
||||
</NewWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import ScreenNotification from "@/screens/Notification/ScreenNotification_V2";
|
||||
import ScreenNotification_V1 from "@/screens/Notification/ScreenNotification_V1";
|
||||
import ScreenNotification_V2 from "@/screens/Notification/ScreenNotification_V2";
|
||||
|
||||
export default function Notification() {
|
||||
return (
|
||||
<>
|
||||
<ScreenNotification />
|
||||
{/* <ScreenNotification_V1 /> */}
|
||||
<ScreenNotification_V2 />
|
||||
{/* <ScreenNotification_V1 /> */}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -29,7 +29,6 @@ export default function ProfileDetailBlocked() {
|
||||
|
||||
const fetchData = async () => {
|
||||
const response = await apiGetBlockedById({ id: String(id) });
|
||||
// console.log("[RESPONSE >>]", JSON.stringify(response, null, 2));
|
||||
setData(response.data);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,59 +1,6 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
LoaderCustom,
|
||||
TextCustom,
|
||||
ViewWrapper
|
||||
} from "@/components";
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
import Voting_BoxPublishSection from "@/screens/Voting/BoxPublishSection";
|
||||
import { apiVotingGetAll } from "@/service/api-client/api-voting";
|
||||
import { useFocusEffect } from "expo-router";
|
||||
import _ from "lodash";
|
||||
import { useState, useCallback } from "react";
|
||||
import Voting_ScreenContribution from "@/screens/Voting/ScreenContribution";
|
||||
|
||||
export default function VotingContribution() {
|
||||
const { user } = useAuth();
|
||||
const [listData, setListData] = useState<any>([]);
|
||||
const [loadingGetData, setLoadingGetData] = useState(false);
|
||||
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
onLoadData();
|
||||
}, [])
|
||||
);
|
||||
|
||||
const onLoadData = async () => {
|
||||
try {
|
||||
setLoadingGetData(true);
|
||||
const response = await apiVotingGetAll({
|
||||
category: "contribution",
|
||||
authorId: user?.id as string,
|
||||
});
|
||||
|
||||
if (response.success) {
|
||||
setListData(response.data);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
} finally {
|
||||
setLoadingGetData(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<ViewWrapper hideFooter>
|
||||
{loadingGetData ? (
|
||||
<LoaderCustom />
|
||||
) : _.isEmpty(listData) ? (
|
||||
<TextCustom align="center">Tidak ada kontribusi</TextCustom>
|
||||
) : listData.map((item: any, index: number) => (
|
||||
<Voting_BoxPublishSection
|
||||
data={item}
|
||||
key={index}
|
||||
href={`/voting/${item.id}/contribution`}
|
||||
/>
|
||||
))}
|
||||
</ViewWrapper>
|
||||
);
|
||||
return <Voting_ScreenContribution />;
|
||||
}
|
||||
|
||||
@@ -1,77 +1,10 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import { LoaderCustom, TextCustom, ViewWrapper } from "@/components";
|
||||
import TabsTwoButtonCustom from "@/components/_ShareComponent/TabsTwoHeaderCustom";
|
||||
import Voting_BoxPublishSection from "@/screens/Voting/BoxPublishSection";
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
import { useCallback, useState } from "react";
|
||||
import { apiVotingGetAll } from "@/service/api-client/api-voting";
|
||||
import { useFocusEffect } from "expo-router";
|
||||
import _ from "lodash";
|
||||
import Voting_ScreenHistory from "@/screens/Voting/ScreenHistory";
|
||||
|
||||
export default function VotingHistory() {
|
||||
const { user } = useAuth();
|
||||
const [activeCategory, setActiveCategory] = useState<string | null>("all");
|
||||
|
||||
const [listData, setListData] = useState<any>([]);
|
||||
const [loadingGetData, setLoadingGetData] = useState(false);
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
onLoadData();
|
||||
}, [activeCategory])
|
||||
);
|
||||
|
||||
const onLoadData = async () => {
|
||||
try {
|
||||
setLoadingGetData(true);
|
||||
const response = await apiVotingGetAll({
|
||||
category: activeCategory === "all" ? "all-history" : "my-history",
|
||||
authorId: user?.id as string,
|
||||
});
|
||||
|
||||
if (response.success) {
|
||||
setListData(response.data);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
} finally {
|
||||
setLoadingGetData(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handlePress = (item: any) => {
|
||||
setActiveCategory(item);
|
||||
// tambahkan logika lain seperti filter dsb.
|
||||
};
|
||||
|
||||
return (
|
||||
<ViewWrapper
|
||||
hideFooter
|
||||
headerComponent={
|
||||
<TabsTwoButtonCustom
|
||||
leftValue="all"
|
||||
rightValue="main"
|
||||
leftText="Semua Riwayat"
|
||||
rightText="Riwayat Saya"
|
||||
activeCategory={activeCategory}
|
||||
handlePress={handlePress}
|
||||
/>
|
||||
}
|
||||
>
|
||||
{loadingGetData ? (
|
||||
<LoaderCustom />
|
||||
) : _.isEmpty(listData) ? (
|
||||
<TextCustom align="center">Tidak ada riwayat</TextCustom>
|
||||
) : (
|
||||
listData.map((item: any, index: number) => (
|
||||
<Voting_BoxPublishSection
|
||||
key={index}
|
||||
id={item.id}
|
||||
data={item}
|
||||
href={`/voting/${item.id}/history`}
|
||||
/>
|
||||
))
|
||||
)}
|
||||
</ViewWrapper>
|
||||
<>
|
||||
<Voting_ScreenHistory />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,71 +1,10 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
FloatingButton,
|
||||
LoaderCustom,
|
||||
SearchInput,
|
||||
TextCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
import Voting_BoxPublishSection from "@/screens/Voting/BoxPublishSection";
|
||||
import { apiVotingGetAll } from "@/service/api-client/api-voting";
|
||||
import { router, useFocusEffect } from "expo-router";
|
||||
import _ from "lodash";
|
||||
import { useCallback, useState } from "react";
|
||||
import Voting_ScreenBeranda from "@/screens/Voting/ScreenBeranda";
|
||||
|
||||
export default function VotingBeranda() {
|
||||
const { user } = useAuth();
|
||||
const [listData, setListData] = useState<any>([]);
|
||||
const [loadingGetData, setLoadingGetData] = useState(false);
|
||||
const [search, setSearch] = useState("");
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
onLoadData();
|
||||
}, [search])
|
||||
);
|
||||
|
||||
const onLoadData = async () => {
|
||||
try {
|
||||
setLoadingGetData(true);
|
||||
const response = await apiVotingGetAll({
|
||||
search,
|
||||
category: "beranda",
|
||||
userLoginId: user?.id,
|
||||
});
|
||||
if (response.success) {
|
||||
setListData(response.data);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
} finally {
|
||||
setLoadingGetData(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<ViewWrapper
|
||||
hideFooter
|
||||
floatingButton={
|
||||
<FloatingButton onPress={() => router.push("/voting/create")} />
|
||||
}
|
||||
headerComponent={
|
||||
<SearchInput placeholder="Cari voting" onChangeText={setSearch} />
|
||||
}
|
||||
>
|
||||
{loadingGetData ? (
|
||||
<LoaderCustom />
|
||||
) : _.isEmpty(listData) ? (
|
||||
<TextCustom align="center">Tidak ada data</TextCustom>
|
||||
) : (
|
||||
listData.map((item: any, index: number) => (
|
||||
<Voting_BoxPublishSection
|
||||
data={item}
|
||||
key={index}
|
||||
href={`/voting/${item.id}`}
|
||||
/>
|
||||
))
|
||||
)}
|
||||
</ViewWrapper>
|
||||
<>
|
||||
<Voting_ScreenBeranda />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -51,8 +51,6 @@ export default function VotingDetailStatus() {
|
||||
setLoadingGetData(true);
|
||||
const response = await apiVotingGetOne({ id: id as string });
|
||||
|
||||
console.log("[DATA BY ID]", JSON.stringify(response, null, 2));
|
||||
|
||||
if (response.success) {
|
||||
setData(response.data);
|
||||
}
|
||||
|
||||
@@ -5,13 +5,14 @@ import {
|
||||
ButtonCustom,
|
||||
CenterCustom,
|
||||
LoaderCustom,
|
||||
NewWrapper,
|
||||
Spacing,
|
||||
StackCustom,
|
||||
TextAreaCustom,
|
||||
TextCustom,
|
||||
TextInputCustom,
|
||||
ViewWrapper,
|
||||
TextInputCustom
|
||||
} from "@/components";
|
||||
import ListSkeletonComponent from "@/components/_ShareComponent/ListSkeletonComponent";
|
||||
import DateTimePickerCustom from "@/components/DateInput/DateTimePickerCustom";
|
||||
import { MainColor } from "@/constants/color-palet";
|
||||
import { ICON_SIZE_XLARGE } from "@/constants/constans-value";
|
||||
@@ -34,7 +35,7 @@ interface IEditData {
|
||||
Voting_DaftarNamaVote?: [
|
||||
{
|
||||
value?: string;
|
||||
}
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
@@ -47,7 +48,7 @@ export default function VotingEdit() {
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
onLoadData();
|
||||
}, [id])
|
||||
}, [id]),
|
||||
);
|
||||
|
||||
const onLoadData = async () => {
|
||||
@@ -188,9 +189,9 @@ export default function VotingEdit() {
|
||||
};
|
||||
|
||||
return (
|
||||
<ViewWrapper footerComponent={buttonSubmit()}>
|
||||
<NewWrapper footerComponent={buttonSubmit()}>
|
||||
{loadingGetData ? (
|
||||
<LoaderCustom />
|
||||
<ListSkeletonComponent />
|
||||
) : (
|
||||
<StackCustom gap={"xs"}>
|
||||
<TextInputCustom
|
||||
@@ -210,7 +211,7 @@ export default function VotingEdit() {
|
||||
onChangeText={(text) => setData({ ...data, deskripsi: text })}
|
||||
/>
|
||||
|
||||
<Spacing />
|
||||
|
||||
|
||||
<DateTimePickerCustom
|
||||
minimumDate={new Date(Date.now())}
|
||||
@@ -255,7 +256,7 @@ export default function VotingEdit() {
|
||||
}
|
||||
</TextCustom>
|
||||
)}
|
||||
<Spacing />
|
||||
|
||||
</StackCustom>
|
||||
|
||||
{data?.Voting_DaftarNamaVote?.map((item: any, index: number) => (
|
||||
@@ -270,7 +271,7 @@ export default function VotingEdit() {
|
||||
...(data as any),
|
||||
Voting_DaftarNamaVote: data?.Voting_DaftarNamaVote?.map(
|
||||
(item: any, i: any) =>
|
||||
i === index ? { ...item, value } : item
|
||||
i === index ? { ...item, value } : item,
|
||||
),
|
||||
})
|
||||
}
|
||||
@@ -327,6 +328,6 @@ export default function VotingEdit() {
|
||||
<Spacing />
|
||||
</StackCustom>
|
||||
)}
|
||||
</ViewWrapper>
|
||||
</NewWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,69 +1,6 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
AvatarUsernameAndOtherComponent,
|
||||
BadgeCustom,
|
||||
BaseBox,
|
||||
LoaderCustom,
|
||||
TextCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import { apiVotingContribution } from "@/service/api-client/api-voting";
|
||||
import { useFocusEffect, useLocalSearchParams } from "expo-router";
|
||||
import _ from "lodash";
|
||||
import { useCallback, useState } from "react";
|
||||
import Voting_ScreenListOfContributor from "@/screens/Voting/ScreenListOfContributor";
|
||||
|
||||
export default function Voting_ListOfContributor() {
|
||||
const { id } = useLocalSearchParams();
|
||||
const [listData, setListData] = useState<any>([]);
|
||||
const [isLoadData, setIsLoadData] = useState(false);
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
onLoadList();
|
||||
}, [id])
|
||||
);
|
||||
|
||||
const onLoadList = async () => {
|
||||
try {
|
||||
setIsLoadData(true);
|
||||
const response = await apiVotingContribution({
|
||||
id: id as string,
|
||||
authorId: "",
|
||||
category: "list",
|
||||
});
|
||||
|
||||
if (response.success) {
|
||||
setListData(response.data);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
} finally {
|
||||
setIsLoadData(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<ViewWrapper>
|
||||
{isLoadData ? (
|
||||
<LoaderCustom />
|
||||
) : _.isEmpty(listData) ? (
|
||||
<TextCustom align="center">Tidak ada kontributor</TextCustom>
|
||||
) : (
|
||||
listData.map((item: any, index: number) => (
|
||||
<BaseBox paddingTop={5} paddingBottom={5} key={index.toString()}>
|
||||
<AvatarUsernameAndOtherComponent
|
||||
avatar={item?.Author?.Profile?.imageId || ""}
|
||||
name={item?.Author?.username || "Username"}
|
||||
avatarHref={`/profile/${item?.Author?.Profile?.id}`}
|
||||
rightComponent={
|
||||
<BadgeCustom style={{ alignSelf: "flex-end" }}>
|
||||
{item?.Voting_DaftarNamaVote?.value}
|
||||
</BadgeCustom>
|
||||
}
|
||||
/>
|
||||
</BaseBox>
|
||||
))
|
||||
)}
|
||||
</ViewWrapper>
|
||||
);
|
||||
export default function VotingListOfContributor() {
|
||||
return <Voting_ScreenListOfContributor />;
|
||||
}
|
||||
|
||||
@@ -80,9 +80,7 @@ export default function VotingCreate() {
|
||||
type: "success",
|
||||
text1: "Data berhasil disimpan",
|
||||
});
|
||||
router.replace(
|
||||
"/(application)/(user)/voting/(tabs)/status?status=review",
|
||||
);
|
||||
router.replace("/voting/(tabs)/status?status=review");
|
||||
} else {
|
||||
Toast.show({
|
||||
type: "error",
|
||||
|
||||
@@ -1,8 +1,36 @@
|
||||
<!-- ===================== Start Penerapan Pagination ===================== -->
|
||||
<!-- ===================== Start Penerapan Pagination Dari Source ===================== -->
|
||||
|
||||
File utama: screens/Voting/ScreenStatus.tsx
|
||||
Function fecth: apiVotingGetByStatus
|
||||
File function fetch: service/api-client/api-voting.ts
|
||||
File source: app/(application)/(user)/donation/[id]/fund-disbursement.tsx
|
||||
Folder tujuan: screens/Donation
|
||||
Nama file utama: ScreenFundDisbursement.tsx
|
||||
|
||||
Buat file baru pada "Folder tujuan" dengan nama "Nama file utama" dan ubah nama function menjadi "Donation_ScreenFundDisbursement" kemudian clean code, import dan panggil function tersebut pada file "File source"
|
||||
Selanjutnya terapkan pagination pada file "Nama file utama"
|
||||
|
||||
Function fecth: apiDonationDisbursementOfFundsListById
|
||||
File function fetch: service/api-client/api-donation.ts
|
||||
File komponen wrapper: components/_ShareComponent/NewWrapper.tsx
|
||||
|
||||
Terapkan pagination pada file "Nama file utama"
|
||||
Analisa juga file "Nama file utama" , jika belum menggunakan NewWrapper pada file "File komponen wrapper" , maka terapkan juga dan ganti wrapper lama yaitu komponen ViewWrapper
|
||||
|
||||
Komponen pagination yang digunaka berada pada file hooks/use-pagination.tsx dan helpers/paginationHelpers.tsx
|
||||
|
||||
Perbaiki fetch "Function fecth" , pada file "File function fetch"
|
||||
Jika tidak ada props page maka tambahkan props page dan default page: "1"
|
||||
|
||||
Gunakan bahasa indonesia pada cli agar saya mudah membacanya.
|
||||
|
||||
<!-- Additional Prompt -->
|
||||
File refrensi: screens/Donation/ScreenListOfNews.tsx
|
||||
Anda bisa menggunakan refrensi dari "File refrensi" jika butuh pemahaman dengan tipe fitur yang sama
|
||||
|
||||
<!-- ===================== End Penerapan Pagination ` ===================== -->
|
||||
|
||||
<!-- ===================== Start Penerapan NewWrapper & Pagination ===================== -->
|
||||
File utama: screens/Donation/ScreenFundDisbursement.tsx
|
||||
Function fecth: apiDonationDisbursementOfFundsListById
|
||||
File function fetch: service/api-client/api-donation.ts
|
||||
File komponen wrapper: components/_ShareComponent/NewWrapper.tsx
|
||||
|
||||
Terapkan pagination pada file "File utama"
|
||||
@@ -15,12 +43,47 @@ Jika tidak ada props page maka tambahkan props page dan default page: "1"
|
||||
|
||||
Gunakan bahasa indonesia pada cli agar saya mudah membacanya.
|
||||
|
||||
File refrensi: screens/Event/ScreenStatus.tsx
|
||||
Anda bisa menggunakan refrensi dari "File refrensi" jika butuh pemahaman dengan tipe fitur yang sama
|
||||
|
||||
<!-- ===================== End Penerapan Pagination ===================== -->
|
||||
<!-- Additinal prompt -->
|
||||
Masukan kode berikut di prop ListHeaderComponent:
|
||||
<InformationBox text="Pencairan dana akan dilakukan oleh Admin HIPMI tanpa campur tangan pihak manapun, jika berita pencairan dana dibawah tidak sesuai dengan kabar yang diberikan oleh PENGGALANG DANA. Maka pegguna lain dapat melaporkannya pada Admin HIPMI !" />
|
||||
<BaseBox>
|
||||
<Grid>
|
||||
<Grid.Col span={6}>
|
||||
<TextCustom bold color="yellow">
|
||||
Rp. {formatCurrencyDisplay(data?.totalPencairan)}
|
||||
</TextCustom>
|
||||
<TextCustom size="small">Total Pencairan Dana</TextCustom>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={6}>
|
||||
<TextCustom bold color="yellow">
|
||||
{data?.akumulasiPencairan} kali
|
||||
</TextCustom>
|
||||
<TextCustom size="small">Akumulasi Pencairan</TextCustom>
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
</BaseBox>
|
||||
|
||||
<!-- ===================== End Penerapan NewWrapper & Pagination ===================== -->
|
||||
|
||||
<!-- Start Penerapan NewWrapper -->
|
||||
Terapkan NewWrapper pada file: screens/Forum/DetailForum.tsx
|
||||
Component yang digunakan: components/_ShareComponent/NewWrapper.tsx , karena ini adalah halaman detail saya ingin anda fokus pada props pada NewWrapper. Seperti
|
||||
<!-- -->
|
||||
Terapkan NewWrapper pada file: app/(application)/(user)/donation/create.tsx
|
||||
Component yang digunakan: components/_ShareComponent/NewWrapper.tsx
|
||||
<!-- End Penerapan NewWrapper -->
|
||||
|
||||
Bantu saya untuk memperbaiki logika path yang ada di dalam file "screens/Admin/Notification-Admin/ScreenNotificationAdmin2.tsx" , pada function fixPath
|
||||
Saya ingin jika didalam deeplink ada "/admin/..." contoh "/admin/event/review/status" maka path yang akan di redirect adalah "/admin/event/review/status"
|
||||
jika tidak maka terapkan sesuai dengan logika yang sudah ada
|
||||
|
||||
Bagaimana menangani bug berikut pada file berikut: screens/Invesment/Document/ScreenRecap.tsx
|
||||
Ini adalah halaman yang memiliki fungsi pagination , saya membuat data dummy dimana menghasilkan data urut 1-9, saya mencoba memuat halaman setiap page nya 4 saja untuk percobaan.
|
||||
Saat awal muncul komponent box dengan data 9 - 6, kemudian saya hapus data ke 8 . lalu saya coba scroll ke bawah seharusnya angka akan tetap urut 9, 7, 6, 5, 4 ... 1. Tapi dalam case ini setelah 8 di hapus kemudian saya scroll box ke 5 tidak muncul saat di scroll. Apakah anda mengerti maksud saya ?
|
||||
|
||||
<!-- COMMIT & PUSH-->
|
||||
Branch: loaddata/10-feb-26
|
||||
Jalankan perintah ini: git checkout -b "Branch"
|
||||
Setelah itu jalankan perintah ini: git add .
|
||||
Setelah itu jalankan perintah ini: git commit -m "
|
||||
<Berikan semua catatan perubahan pada branch ini, tampilan pada saya dan pastikan dalam bahasa indonesia. Saya akan cek baru saya akan berikan perintah push>
|
||||
"
|
||||
Setelah itu jalankan perintah ini: git push origin "Branch"
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
import { IconPlus } from "@/components/_Icon";
|
||||
import { IconDot } from "@/components/_Icon/IconComponent";
|
||||
import { AccentColor, MainColor } from "@/constants/color-palet";
|
||||
import { ICON_SIZE_SMALL } from "@/constants/constans-value";
|
||||
import { ICON_SIZE_SMALL, PAGINATION_DEFAULT_TAKE } from "@/constants/constans-value";
|
||||
import { createPaginationComponents } from "@/helpers/paginationHelpers";
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
import { useNotificationStore } from "@/hooks/use-notification-store";
|
||||
@@ -26,8 +26,6 @@ import _ from "lodash";
|
||||
import { useState } from "react";
|
||||
import { RefreshControl, View } from "react-native";
|
||||
|
||||
const PAGE_SIZE = 10;
|
||||
|
||||
const selectedCategory = (value: string) => {
|
||||
const category = listOfcategoriesAppNotification.find(
|
||||
(c) => c.value === value,
|
||||
@@ -35,6 +33,37 @@ const selectedCategory = (value: string) => {
|
||||
return category?.label;
|
||||
};
|
||||
|
||||
const fixPath = ({
|
||||
deepLink,
|
||||
categoryApp,
|
||||
}: {
|
||||
deepLink: string;
|
||||
categoryApp: string;
|
||||
}) => {
|
||||
// Jika categoryApp adalah "OTHER", kembalikan deepLink tanpa perubahan
|
||||
if (categoryApp === "OTHER") {
|
||||
return deepLink;
|
||||
}
|
||||
|
||||
// Jika dalam deepLink terdapat "/admin/", kembalikan path tersebut tanpa modifikasi tambahan
|
||||
if (deepLink.includes("/admin/")) {
|
||||
return deepLink;
|
||||
}
|
||||
|
||||
console.log("Category App", categoryApp);
|
||||
console.log("Deep Link", deepLink);
|
||||
|
||||
const separator = deepLink.includes("?") ? "&" : "?";
|
||||
|
||||
const fixedPath = `${deepLink}${separator}from=notifications&category=${_.lowerCase(
|
||||
categoryApp,
|
||||
)}`;
|
||||
|
||||
console.log("Fix Path", fixedPath);
|
||||
|
||||
return fixedPath;
|
||||
};
|
||||
|
||||
const BoxNotification = ({
|
||||
data,
|
||||
activeCategory,
|
||||
@@ -50,17 +79,22 @@ const BoxNotification = ({
|
||||
<BaseBox
|
||||
backgroundColor={data.isRead ? AccentColor.darkblue : AccentColor.blue}
|
||||
onPress={() => {
|
||||
console.log(
|
||||
"Notification >",
|
||||
selectedCategory(activeCategory as string),
|
||||
);
|
||||
router.push(data.deepLink);
|
||||
markAsRead(data.id);
|
||||
setListData((prev: any) =>
|
||||
prev.map((item: any) =>
|
||||
item.id === data.id ? { ...item, isRead: true } : item,
|
||||
),
|
||||
);
|
||||
const newPath = fixPath({
|
||||
deepLink: data.deepLink,
|
||||
categoryApp: data.kategoriApp,
|
||||
});
|
||||
|
||||
selectedCategory(activeCategory as string);
|
||||
router.navigate(newPath as any);
|
||||
|
||||
if (!data.isRead) {
|
||||
markAsRead(data.id);
|
||||
setListData((prev: any) =>
|
||||
prev.map((item: any) =>
|
||||
item.id === data.id ? { ...item, isRead: true } : item,
|
||||
),
|
||||
);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<StackCustom>
|
||||
@@ -97,7 +131,7 @@ export default function Admin_ScreenNotification2() {
|
||||
page: String(page),
|
||||
});
|
||||
},
|
||||
pageSize: PAGE_SIZE,
|
||||
pageSize: PAGINATION_DEFAULT_TAKE,
|
||||
dependencies: [user?.id, activeCategory],
|
||||
onError: (error) =>
|
||||
console.error("[ERROR] Fetch admin notifications:", error),
|
||||
@@ -110,7 +144,7 @@ export default function Admin_ScreenNotification2() {
|
||||
refreshing: pagination.refreshing,
|
||||
listData: pagination.listData,
|
||||
emptyMessage: "Belum ada notifikasi",
|
||||
skeletonCount: 5,
|
||||
skeletonCount: PAGINATION_DEFAULT_TAKE,
|
||||
skeletonHeight: 100,
|
||||
});
|
||||
|
||||
|
||||
21
screens/Donation/BoxNews.tsx
Normal file
21
screens/Donation/BoxNews.tsx
Normal file
@@ -0,0 +1,21 @@
|
||||
import { BaseBox, Grid, Spacing, TextCustom } from "@/components";
|
||||
import { formatChatTime } from "@/utils/formatChatTime";
|
||||
|
||||
export default function Donation_BoxNews({item}: {item: any}){
|
||||
return <>
|
||||
<BaseBox href={`/donation/[id]/(news)/${item?.id}`}>
|
||||
<Grid>
|
||||
<Grid.Col span={8}>
|
||||
<TextCustom truncate bold>
|
||||
{item?.title || "-"}
|
||||
</TextCustom>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={4} style={{ alignItems: "flex-end" }}>
|
||||
<TextCustom size="small">
|
||||
{formatChatTime(item?.createdAt)}
|
||||
</TextCustom>
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
</BaseBox>
|
||||
</>
|
||||
}
|
||||
@@ -16,6 +16,9 @@ export default function Donation_ButtonStatusSection({
|
||||
}) {
|
||||
const [isLoading, setLoading] = useState(false);
|
||||
const [isLoadingDelete, setLoadingDelete] = useState(false);
|
||||
const path: any = (status: string) => {
|
||||
return `/donation/(tabs)/status?status=${status}`;
|
||||
};
|
||||
const handleBatalkanReview = async () => {
|
||||
AlertDefaultSystem({
|
||||
title: "Batalkan Review",
|
||||
@@ -43,7 +46,7 @@ export default function Donation_ButtonStatusSection({
|
||||
text1: response.message,
|
||||
});
|
||||
|
||||
router.back();
|
||||
router.push(path("draft"));
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
} finally {
|
||||
@@ -80,7 +83,7 @@ export default function Donation_ButtonStatusSection({
|
||||
text1: response.message,
|
||||
});
|
||||
|
||||
router.back();
|
||||
router.replace(path("review"));
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
} finally {
|
||||
@@ -117,7 +120,7 @@ export default function Donation_ButtonStatusSection({
|
||||
text1: response.message,
|
||||
});
|
||||
|
||||
router.back();
|
||||
router.replace(path("draft"));
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
} finally {
|
||||
|
||||
@@ -12,11 +12,13 @@ import { View } from "react-native";
|
||||
export default function Donation_ComponentBoxDetailData({
|
||||
bottomSection,
|
||||
data,
|
||||
showSisaHari = true,
|
||||
sisaHari,
|
||||
reminder,
|
||||
}: {
|
||||
bottomSection?: React.ReactNode;
|
||||
data: any;
|
||||
showSisaHari?: boolean;
|
||||
sisaHari: number;
|
||||
reminder: boolean;
|
||||
}) {
|
||||
@@ -34,9 +36,9 @@ export default function Donation_ComponentBoxDetailData({
|
||||
<TextCustom bold color="red">
|
||||
Waktu berakhir
|
||||
</TextCustom>
|
||||
) : (
|
||||
) : showSisaHari ? (
|
||||
<TextCustom>Sisa hari: {sisaHari}</TextCustom>
|
||||
)}
|
||||
) : null}
|
||||
</View>
|
||||
|
||||
<Grid>
|
||||
|
||||
62
screens/Donation/ScreenBeranda.tsx
Normal file
62
screens/Donation/ScreenBeranda.tsx
Normal file
@@ -0,0 +1,62 @@
|
||||
import FloatingButton from "@/components/Button/FloatingButton";
|
||||
import NewWrapper from "@/components/_ShareComponent/NewWrapper";
|
||||
import { MainColor } from "@/constants/color-palet";
|
||||
import { PAGINATION_DEFAULT_TAKE } from "@/constants/constans-value";
|
||||
import { createPaginationComponents } from "@/helpers/paginationHelpers";
|
||||
import { usePagination } from "@/hooks/use-pagination";
|
||||
import Donation_BoxPublish from "@/screens/Donation/BoxPublish";
|
||||
import { apiDonationGetAll } from "@/service/api-client/api-donation";
|
||||
import { router } from "expo-router";
|
||||
import { RefreshControl } from "react-native";
|
||||
|
||||
export default function Donation_ScreenBeranda() {
|
||||
const pagination = usePagination({
|
||||
fetchFunction: async (page, search) => {
|
||||
return await apiDonationGetAll({
|
||||
category: "beranda",
|
||||
page: String(page),
|
||||
}).then((res) => {
|
||||
console.log("RES", JSON.stringify(res, null, 2));
|
||||
return res;
|
||||
});
|
||||
},
|
||||
pageSize: PAGINATION_DEFAULT_TAKE, // Sesuaikan dengan jumlah item per halaman yang diinginkan
|
||||
onError: (error) => console.error("[ERROR] Fetch event beranda:", error),
|
||||
dependencies: [],
|
||||
});
|
||||
|
||||
const { ListEmptyComponent, ListFooterComponent } =
|
||||
createPaginationComponents({
|
||||
loading: pagination.loading,
|
||||
refreshing: pagination.refreshing,
|
||||
listData: pagination.listData,
|
||||
isInitialLoad: pagination.isInitialLoad,
|
||||
emptyMessage: "Belum ada donasi",
|
||||
skeletonCount: PAGINATION_DEFAULT_TAKE,
|
||||
skeletonHeight: 150,
|
||||
});
|
||||
|
||||
return (
|
||||
<NewWrapper
|
||||
listData={pagination.listData}
|
||||
renderItem={({ item }) => (
|
||||
<Donation_BoxPublish data={item} id={item.id} />
|
||||
)}
|
||||
onEndReached={pagination.loadMore}
|
||||
ListEmptyComponent={ListEmptyComponent}
|
||||
ListFooterComponent={ListFooterComponent}
|
||||
refreshControl={
|
||||
<RefreshControl
|
||||
refreshing={pagination.refreshing}
|
||||
onRefresh={pagination.onRefresh}
|
||||
tintColor={MainColor.yellow}
|
||||
colors={[MainColor.yellow]}
|
||||
/>
|
||||
}
|
||||
hideFooter
|
||||
floatingButton={
|
||||
<FloatingButton onPress={() => router.push("/donation/create")} />
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
174
screens/Donation/ScreenFundDisbursement.tsx
Normal file
174
screens/Donation/ScreenFundDisbursement.tsx
Normal file
@@ -0,0 +1,174 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
ActionIcon,
|
||||
BaseBox,
|
||||
Grid,
|
||||
InformationBox,
|
||||
Spacing,
|
||||
StackCustom,
|
||||
TextCustom,
|
||||
} from "@/components";
|
||||
import { MainColor } from "@/constants/color-palet";
|
||||
import { usePagination } from "@/hooks/use-pagination";
|
||||
import {
|
||||
apiDonationDisbursementOfFundsListById,
|
||||
apiDonationGetOne,
|
||||
} from "@/service/api-client/api-donation";
|
||||
import { formatCurrencyDisplay } from "@/utils/formatCurrencyDisplay";
|
||||
import { Feather } from "@expo/vector-icons";
|
||||
import dayjs from "dayjs";
|
||||
import { router, useLocalSearchParams } from "expo-router";
|
||||
import _ from "lodash";
|
||||
import React, { useState } from "react";
|
||||
import { RefreshControl, View } from "react-native";
|
||||
import { createPaginationComponents } from "@/helpers/paginationHelpers";
|
||||
import { PAGINATION_DEFAULT_TAKE } from "@/constants/constans-value";
|
||||
import NewWrapper from "@/components/_ShareComponent/NewWrapper";
|
||||
import { Divider } from "react-native-paper";
|
||||
|
||||
interface Donation_ScreenFundDisbursementProps {
|
||||
donationId: string;
|
||||
}
|
||||
|
||||
export default function Donation_ScreenFundDisbursement({
|
||||
donationId,
|
||||
}: Donation_ScreenFundDisbursementProps) {
|
||||
const [data, setData] = useState({
|
||||
totalPencairan: 0,
|
||||
akumulasiPencairan: 0,
|
||||
});
|
||||
|
||||
// Ambil data utama (total pencairan, dll) terpisah dari pagination
|
||||
React.useEffect(() => {
|
||||
const onLoadData = async () => {
|
||||
try {
|
||||
const responseData = await apiDonationGetOne({
|
||||
id: donationId,
|
||||
category: "permanent",
|
||||
});
|
||||
|
||||
if (responseData.success) {
|
||||
setData({
|
||||
totalPencairan: responseData.data.totalPencairan,
|
||||
akumulasiPencairan: responseData.data.akumulasiPencairan,
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
}
|
||||
};
|
||||
|
||||
onLoadData();
|
||||
}, [donationId]);
|
||||
|
||||
const pagination = usePagination({
|
||||
fetchFunction: async (page) => {
|
||||
return await apiDonationDisbursementOfFundsListById({
|
||||
id: donationId,
|
||||
page: String(page),
|
||||
});
|
||||
},
|
||||
pageSize: PAGINATION_DEFAULT_TAKE, // Sesuaikan dengan jumlah item per halaman dari API
|
||||
dependencies: [donationId],
|
||||
});
|
||||
|
||||
const renderItem = ({ item, index }: { item: any; index: number }) => (
|
||||
<BaseBox key={index}>
|
||||
<StackCustom>
|
||||
<Grid>
|
||||
<Grid.Col span={8}>
|
||||
<TextCustom bold>{item?.title}</TextCustom>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={4} style={{ alignItems: "flex-end" }}>
|
||||
<TextCustom size="small">
|
||||
{dayjs(item?.createdAt).format("DD MMM YYYY")}
|
||||
</TextCustom>
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
<TextCustom>{item?.deskripsi}</TextCustom>
|
||||
{/* <Spacing /> */}
|
||||
<Divider />
|
||||
<Grid>
|
||||
<Grid.Col span={8} style={{ alignSelf: "center" }}>
|
||||
<TextCustom bold size={"large"}>
|
||||
Rp. {formatCurrencyDisplay(item?.nominalCair)}
|
||||
</TextCustom>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={4} style={{ alignItems: "flex-end" }}>
|
||||
<ActionIcon
|
||||
icon={<Feather name="file-text" color={MainColor.black} />}
|
||||
onPress={() => {
|
||||
router.navigate(
|
||||
`/(application)/(image)/preview-image/${item?.imageId}`,
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
{/*
|
||||
<ButtonCenteredOnly
|
||||
onPress={() => {
|
||||
router.navigate(
|
||||
`/(application)/(image)/preview-image/${item?.imageId}`,
|
||||
);
|
||||
}}
|
||||
icon="file-text"
|
||||
>
|
||||
Bukti Transaksi
|
||||
</ButtonCenteredOnly> */}
|
||||
</StackCustom>
|
||||
</BaseBox>
|
||||
);
|
||||
|
||||
const { ListEmptyComponent, ListFooterComponent } =
|
||||
createPaginationComponents({
|
||||
loading: pagination.loading,
|
||||
refreshing: pagination.refreshing,
|
||||
listData: pagination.listData,
|
||||
isInitialLoad: pagination.isInitialLoad,
|
||||
emptyMessage: "Belum ada data",
|
||||
skeletonCount: PAGINATION_DEFAULT_TAKE,
|
||||
skeletonHeight: 150,
|
||||
});
|
||||
|
||||
// Komponen header yang akan ditampilkan di atas daftar
|
||||
const ListHeaderComponent = (
|
||||
<View>
|
||||
<InformationBox text="Pencairan dana akan dilakukan oleh Admin HIPMI tanpa campur tangan pihak manapun, jika berita pencairan dana dibawah tidak sesuai dengan kabar yang diberikan oleh PENGGALANG DANA. Maka pegguna lain dapat melaporkannya pada Admin HIPMI !" />
|
||||
<BaseBox>
|
||||
<Grid>
|
||||
<Grid.Col span={6}>
|
||||
<TextCustom bold color="yellow">
|
||||
Rp. {formatCurrencyDisplay(data?.totalPencairan)}
|
||||
</TextCustom>
|
||||
<TextCustom size="small">Total Pencairan Dana</TextCustom>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={6}>
|
||||
<TextCustom bold color="yellow">
|
||||
{data?.akumulasiPencairan} kali
|
||||
</TextCustom>
|
||||
<TextCustom size="small">Akumulasi Pencairan</TextCustom>
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
</BaseBox>
|
||||
</View>
|
||||
);
|
||||
|
||||
return (
|
||||
<NewWrapper
|
||||
listData={pagination.listData}
|
||||
renderItem={renderItem}
|
||||
onEndReached={pagination.loadMore}
|
||||
ListEmptyComponent={ListEmptyComponent}
|
||||
ListFooterComponent={ListFooterComponent}
|
||||
ListHeaderComponent={ListHeaderComponent}
|
||||
refreshControl={
|
||||
<RefreshControl
|
||||
refreshing={pagination.refreshing}
|
||||
onRefresh={pagination.onRefresh}
|
||||
/>
|
||||
}
|
||||
hideFooter
|
||||
/>
|
||||
);
|
||||
}
|
||||
98
screens/Donation/ScreenListOfDonatur.tsx
Normal file
98
screens/Donation/ScreenListOfDonatur.tsx
Normal file
@@ -0,0 +1,98 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
BaseBox,
|
||||
Grid,
|
||||
LoaderCustom,
|
||||
Spacing,
|
||||
StackCustom,
|
||||
TextCustom,
|
||||
} from "@/components";
|
||||
import { MainColor } from "@/constants/color-palet";
|
||||
import { usePagination } from "@/hooks/use-pagination";
|
||||
import { apiDonationListOfDonaturById } from "@/service/api-client/api-donation";
|
||||
import { formatCurrencyDisplay } from "@/utils/formatCurrencyDisplay";
|
||||
import { FontAwesome6 } from "@expo/vector-icons";
|
||||
import dayjs from "dayjs";
|
||||
import { RefreshControl } from "react-native";
|
||||
import { createPaginationComponents } from "@/helpers/paginationHelpers";
|
||||
import { PAGINATION_DEFAULT_TAKE } from "@/constants/constans-value";
|
||||
import NewWrapper from "@/components/_ShareComponent/NewWrapper";
|
||||
|
||||
interface Donation_ScreenListOfDonaturProps {
|
||||
donationId: string;
|
||||
}
|
||||
|
||||
export default function Donation_ScreenListOfDonatur({
|
||||
donationId,
|
||||
}: Donation_ScreenListOfDonaturProps) {
|
||||
const pagination = usePagination({
|
||||
fetchFunction: async (page) => {
|
||||
return await apiDonationListOfDonaturById({
|
||||
id: donationId,
|
||||
page: String(page),
|
||||
});
|
||||
},
|
||||
pageSize: PAGINATION_DEFAULT_TAKE, // Sesuaikan dengan jumlah item per halaman dari API
|
||||
dependencies: [donationId],
|
||||
});
|
||||
|
||||
const renderItem = ({ item, index }: { item: any; index: number }) => (
|
||||
<BaseBox key={index}>
|
||||
<Grid>
|
||||
<Grid.Col
|
||||
span={3}
|
||||
style={{ alignItems: "center", justifyContent: "center" }}
|
||||
>
|
||||
<FontAwesome6
|
||||
name="face-smile-wink"
|
||||
size={50}
|
||||
style={{ color: MainColor.yellow }}
|
||||
/>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={9}>
|
||||
<TextCustom bold size="large">
|
||||
{item?.Author?.username || "-"}
|
||||
</TextCustom>
|
||||
<Spacing />
|
||||
<StackCustom gap={"xs"}>
|
||||
<TextCustom size={"small"}>Berdonas sebesar </TextCustom>
|
||||
<TextCustom bold size="large" color="yellow">
|
||||
Rp. {formatCurrencyDisplay(item?.nominal)}
|
||||
</TextCustom>
|
||||
<TextCustom>
|
||||
{dayjs(item?.createdAt).format("DD MMM YYYY, HH:mm")}
|
||||
</TextCustom>
|
||||
</StackCustom>
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
</BaseBox>
|
||||
);
|
||||
|
||||
const { ListEmptyComponent, ListFooterComponent } =
|
||||
createPaginationComponents({
|
||||
loading: pagination.loading,
|
||||
refreshing: pagination.refreshing,
|
||||
listData: pagination.listData,
|
||||
isInitialLoad: pagination.isInitialLoad,
|
||||
emptyMessage: "Belum ada donatur",
|
||||
skeletonCount: PAGINATION_DEFAULT_TAKE,
|
||||
skeletonHeight: 120,
|
||||
});
|
||||
|
||||
return (
|
||||
<NewWrapper
|
||||
hideFooter
|
||||
listData={pagination.listData}
|
||||
renderItem={renderItem}
|
||||
onEndReached={pagination.loadMore}
|
||||
ListEmptyComponent={ListEmptyComponent}
|
||||
ListFooterComponent={ListFooterComponent}
|
||||
refreshControl={
|
||||
<RefreshControl
|
||||
refreshing={pagination.refreshing}
|
||||
onRefresh={pagination.onRefresh}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
97
screens/Donation/ScreenListOfNews.tsx
Normal file
97
screens/Donation/ScreenListOfNews.tsx
Normal file
@@ -0,0 +1,97 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import { BackButton, DrawerCustom, MenuDrawerDynamicGrid } from "@/components";
|
||||
import { IconPlus } from "@/components/_Icon";
|
||||
import NewWrapper from "@/components/_ShareComponent/NewWrapper";
|
||||
import { MainColor } from "@/constants/color-palet";
|
||||
import { PAGINATION_DEFAULT_TAKE } from "@/constants/constans-value";
|
||||
import { createPaginationComponents } from "@/helpers/paginationHelpers";
|
||||
import { usePagination } from "@/hooks/use-pagination";
|
||||
import { apiDonationGetNewsById } from "@/service/api-client/api-donation";
|
||||
import { router, Stack } from "expo-router";
|
||||
import { useState } from "react";
|
||||
import { RefreshControl } from "react-native";
|
||||
import Donation_BoxNews from "./BoxNews";
|
||||
|
||||
interface Donation_ScreenListOfNewsProps {
|
||||
donationId: string;
|
||||
}
|
||||
|
||||
export default function Donation_ScreenListOfNews({
|
||||
donationId,
|
||||
}: Donation_ScreenListOfNewsProps) {
|
||||
const [openDrawer, setOpenDrawer] = useState(false);
|
||||
|
||||
const pagination = usePagination({
|
||||
fetchFunction: async (page) => {
|
||||
return await apiDonationGetNewsById({
|
||||
id: donationId,
|
||||
category: "get-all",
|
||||
page: String(page),
|
||||
});
|
||||
},
|
||||
pageSize: PAGINATION_DEFAULT_TAKE, // Sesuaikan dengan jumlah item per halaman dari API
|
||||
dependencies: [donationId],
|
||||
});
|
||||
|
||||
const renderItem = ({ item, index }: { item: any; index: number }) => (
|
||||
<Donation_BoxNews key={index} item={item} />
|
||||
);
|
||||
|
||||
const { ListEmptyComponent, ListFooterComponent } =
|
||||
createPaginationComponents({
|
||||
loading: pagination.loading,
|
||||
refreshing: pagination.refreshing,
|
||||
listData: pagination.listData,
|
||||
isInitialLoad: pagination.isInitialLoad,
|
||||
emptyMessage: "Tidak ada kabar",
|
||||
skeletonCount: PAGINATION_DEFAULT_TAKE,
|
||||
skeletonHeight: 80,
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<Stack.Screen
|
||||
options={{
|
||||
title: "Daftar Kabar",
|
||||
headerLeft: () => <BackButton />,
|
||||
}}
|
||||
/>
|
||||
<NewWrapper
|
||||
listData={pagination.listData}
|
||||
renderItem={renderItem}
|
||||
onEndReached={pagination.loadMore}
|
||||
ListEmptyComponent={ListEmptyComponent}
|
||||
ListFooterComponent={ListFooterComponent}
|
||||
refreshControl={
|
||||
<RefreshControl
|
||||
refreshing={pagination.refreshing}
|
||||
onRefresh={pagination.onRefresh}
|
||||
tintColor={MainColor.yellow}
|
||||
colors={[MainColor.yellow]}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
|
||||
<DrawerCustom
|
||||
isVisible={openDrawer}
|
||||
closeDrawer={() => setOpenDrawer(false)}
|
||||
height={"auto"}
|
||||
>
|
||||
<MenuDrawerDynamicGrid
|
||||
data={[
|
||||
{
|
||||
icon: <IconPlus />,
|
||||
label: "Tambah Berita",
|
||||
path: `/donation/${donationId}/(news)/add-news`,
|
||||
},
|
||||
]}
|
||||
onPressItem={(item) => {
|
||||
console.log("PATH ", item.path);
|
||||
router.navigate(item.path as any);
|
||||
setOpenDrawer(false);
|
||||
}}
|
||||
/>
|
||||
</DrawerCustom>
|
||||
</>
|
||||
);
|
||||
}
|
||||
152
screens/Donation/ScreenMyDonation.tsx
Normal file
152
screens/Donation/ScreenMyDonation.tsx
Normal file
@@ -0,0 +1,152 @@
|
||||
import {
|
||||
BadgeCustom,
|
||||
BaseBox,
|
||||
DummyLandscapeImage,
|
||||
Grid,
|
||||
StackCustom,
|
||||
TextCustom,
|
||||
} from "@/components";
|
||||
import FloatingButton from "@/components/Button/FloatingButton";
|
||||
import NewWrapper from "@/components/_ShareComponent/NewWrapper";
|
||||
import { MainColor } from "@/constants/color-palet";
|
||||
import { PAGINATION_DEFAULT_TAKE } from "@/constants/constans-value";
|
||||
import { createPaginationComponents } from "@/helpers/paginationHelpers";
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
import { usePagination } from "@/hooks/use-pagination";
|
||||
import { apiDonationGetAll } from "@/service/api-client/api-donation";
|
||||
import { formatCurrencyDisplay } from "@/utils/formatCurrencyDisplay";
|
||||
import { Href, router } from "expo-router";
|
||||
import _ from "lodash";
|
||||
import { RefreshControl, View } from "react-native";
|
||||
|
||||
export default function Donation_ScreenMyDonation() {
|
||||
const { user } = useAuth();
|
||||
|
||||
const pagination = usePagination({
|
||||
fetchFunction: async (page, search) => {
|
||||
if (!user?.id) {
|
||||
throw new Error("User tidak ditemukan");
|
||||
}
|
||||
|
||||
return await apiDonationGetAll({
|
||||
category: "my-donation",
|
||||
authorId: user?.id,
|
||||
page: String(page),
|
||||
});
|
||||
},
|
||||
pageSize: PAGINATION_DEFAULT_TAKE, // Sesuaikan dengan jumlah item per halaman yang diinginkan
|
||||
dependencies: [user?.id], // Reload ketika user.id berubah
|
||||
});
|
||||
|
||||
const { ListEmptyComponent, ListFooterComponent } =
|
||||
createPaginationComponents({
|
||||
loading: pagination.loading,
|
||||
refreshing: pagination.refreshing,
|
||||
listData: pagination.listData,
|
||||
isInitialLoad: pagination.isInitialLoad,
|
||||
emptyMessage: "Belum ada transaksi",
|
||||
skeletonCount: PAGINATION_DEFAULT_TAKE,
|
||||
skeletonHeight: 150,
|
||||
});
|
||||
|
||||
const handlerColor = (status: string) => {
|
||||
if (status === "menunggu") {
|
||||
return "orange";
|
||||
} else if (status === "proses") {
|
||||
return "white";
|
||||
} else if (status === "berhasil") {
|
||||
return "green";
|
||||
} else if (status === "gagal") {
|
||||
return "red";
|
||||
}
|
||||
};
|
||||
|
||||
const handlePress = ({
|
||||
invoiceId,
|
||||
donationId,
|
||||
status,
|
||||
}: {
|
||||
invoiceId: string;
|
||||
donationId: string;
|
||||
status: string;
|
||||
}) => {
|
||||
const url: Href = `../${donationId}/(transaction-flow)/${invoiceId}`;
|
||||
if (status === "menunggu") {
|
||||
router.push(`${url}/invoice`);
|
||||
} else if (status === "proses") {
|
||||
router.push(`${url}/process`);
|
||||
} else if (status === "berhasil") {
|
||||
router.push(`${url}/success`);
|
||||
} else if (status === "gagal") {
|
||||
router.push(`${url}/failed`);
|
||||
}
|
||||
};
|
||||
|
||||
const renderItem = ({ item }: { item: any }) => (
|
||||
<BaseBox
|
||||
paddingTop={7}
|
||||
paddingBottom={7}
|
||||
onPress={() => {
|
||||
handlePress({
|
||||
status: _.lowerCase(item.statusInvoice),
|
||||
invoiceId: item.id,
|
||||
donationId: item.donasiId,
|
||||
});
|
||||
}}
|
||||
>
|
||||
<Grid>
|
||||
<Grid.Col span={5}>
|
||||
<DummyLandscapeImage
|
||||
height={100}
|
||||
unClickPath
|
||||
imageId={item.imageId}
|
||||
/>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={1}>
|
||||
<View />
|
||||
</Grid.Col>
|
||||
<Grid.Col span={6}>
|
||||
<StackCustom>
|
||||
<TextCustom truncate={2} bold>
|
||||
{item.title || "-"}
|
||||
</TextCustom>
|
||||
|
||||
<TextCustom bold color="yellow">
|
||||
Rp. {formatCurrencyDisplay(item.nominal)}
|
||||
</TextCustom>
|
||||
|
||||
<BadgeCustom
|
||||
variant="light"
|
||||
color={handlerColor(_.lowerCase(item.statusInvoice))}
|
||||
fullWidth
|
||||
>
|
||||
{item.statusInvoice}
|
||||
</BadgeCustom>
|
||||
</StackCustom>
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
</BaseBox>
|
||||
);
|
||||
|
||||
return (
|
||||
<NewWrapper
|
||||
listData={pagination.listData}
|
||||
renderItem={renderItem}
|
||||
onEndReached={pagination.loadMore}
|
||||
ListEmptyComponent={ListEmptyComponent}
|
||||
ListFooterComponent={ListFooterComponent}
|
||||
refreshControl={
|
||||
<RefreshControl
|
||||
refreshing={pagination.refreshing}
|
||||
onRefresh={pagination.onRefresh}
|
||||
tintColor={MainColor.yellow}
|
||||
colors={[MainColor.yellow]}
|
||||
/>
|
||||
}
|
||||
hideFooter
|
||||
floatingButton={
|
||||
<FloatingButton onPress={() => router.push("/donation/create")} />
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
105
screens/Donation/ScreenRecapOfNews.tsx
Normal file
105
screens/Donation/ScreenRecapOfNews.tsx
Normal file
@@ -0,0 +1,105 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
BackButton,
|
||||
DotButton,
|
||||
DrawerCustom,
|
||||
MenuDrawerDynamicGrid,
|
||||
} from "@/components";
|
||||
import { IconPlus } from "@/components/_Icon";
|
||||
import NewWrapper from "@/components/_ShareComponent/NewWrapper";
|
||||
import { PAGINATION_DEFAULT_TAKE } from "@/constants/constans-value";
|
||||
import { createPaginationComponents } from "@/helpers/paginationHelpers";
|
||||
import { usePagination } from "@/hooks/use-pagination";
|
||||
import { apiDonationGetNewsById } from "@/service/api-client/api-donation";
|
||||
import { router, Stack, useFocusEffect } from "expo-router";
|
||||
import { useCallback, useState } from "react";
|
||||
import { RefreshControl } from "react-native";
|
||||
import Donation_BoxNews from "./BoxNews";
|
||||
|
||||
interface Donation_ScreenRecapOfNewsProps {
|
||||
donationId: string;
|
||||
}
|
||||
|
||||
export default function Donation_ScreenRecapOfNews({
|
||||
donationId,
|
||||
}: Donation_ScreenRecapOfNewsProps) {
|
||||
const [openDrawer, setOpenDrawer] = useState(false);
|
||||
|
||||
const pagination = usePagination({
|
||||
fetchFunction: async (page) => {
|
||||
return await apiDonationGetNewsById({
|
||||
id: donationId,
|
||||
category: "get-all",
|
||||
page: String(page),
|
||||
});
|
||||
},
|
||||
pageSize: PAGINATION_DEFAULT_TAKE, // Sesuaikan dengan jumlah item per halaman dari API
|
||||
dependencies: [donationId],
|
||||
});
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
pagination.onRefresh();
|
||||
}, [donationId]),
|
||||
);
|
||||
|
||||
const renderItem = ({ item, index }: { item: any; index: number }) => (
|
||||
<Donation_BoxNews key={index} item={item} />
|
||||
);
|
||||
const { ListEmptyComponent, ListFooterComponent } =
|
||||
createPaginationComponents({
|
||||
loading: pagination.loading,
|
||||
refreshing: pagination.refreshing,
|
||||
listData: pagination.listData,
|
||||
isInitialLoad: pagination.isInitialLoad,
|
||||
emptyMessage: "Tidak ada kabar",
|
||||
skeletonCount: PAGINATION_DEFAULT_TAKE,
|
||||
skeletonHeight: 80,
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<Stack.Screen
|
||||
options={{
|
||||
title: "Rekap Kabar",
|
||||
headerLeft: () => <BackButton />,
|
||||
headerRight: () => <DotButton onPress={() => setOpenDrawer(true)} />,
|
||||
}}
|
||||
/>
|
||||
<NewWrapper
|
||||
listData={pagination.listData}
|
||||
renderItem={renderItem}
|
||||
onEndReached={pagination.loadMore}
|
||||
ListEmptyComponent={ListEmptyComponent}
|
||||
ListFooterComponent={ListFooterComponent}
|
||||
refreshControl={
|
||||
<RefreshControl
|
||||
refreshing={pagination.refreshing}
|
||||
onRefresh={pagination.onRefresh}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
|
||||
<DrawerCustom
|
||||
isVisible={openDrawer}
|
||||
closeDrawer={() => setOpenDrawer(false)}
|
||||
height={"auto"}
|
||||
>
|
||||
<MenuDrawerDynamicGrid
|
||||
data={[
|
||||
{
|
||||
icon: <IconPlus />,
|
||||
label: "Tambah Berita",
|
||||
path: `/donation/${donationId}/(news)/add-news`,
|
||||
},
|
||||
]}
|
||||
onPressItem={(item) => {
|
||||
console.log("PATH ", item.path);
|
||||
router.navigate(item.path as any);
|
||||
setOpenDrawer(false);
|
||||
}}
|
||||
/>
|
||||
</DrawerCustom>
|
||||
</>
|
||||
);
|
||||
}
|
||||
97
screens/Donation/ScreenStatus.tsx
Normal file
97
screens/Donation/ScreenStatus.tsx
Normal file
@@ -0,0 +1,97 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import { ScrollableCustom } from "@/components";
|
||||
import NewWrapper from "@/components/_ShareComponent/NewWrapper";
|
||||
import { MainColor } from "@/constants/color-palet";
|
||||
import { PAGINATION_DEFAULT_TAKE } from "@/constants/constans-value";
|
||||
import { createPaginationComponents } from "@/helpers/paginationHelpers";
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
import { usePagination } from "@/hooks/use-pagination";
|
||||
import { dummyMasterStatus } from "@/lib/dummy-data/_master/status";
|
||||
import Donasi_BoxStatus from "@/screens/Donation/BoxStatus";
|
||||
import { apiDonationGetByStatus } from "@/service/api-client/api-donation";
|
||||
import { useFocusEffect } from "expo-router";
|
||||
import { useCallback, useState } from "react";
|
||||
import { RefreshControl } from "react-native";
|
||||
|
||||
interface DonationStatusProps {
|
||||
initialStatus?: string;
|
||||
}
|
||||
|
||||
export default function Donation_ScreenStatus({
|
||||
initialStatus = "publish",
|
||||
}: DonationStatusProps) {
|
||||
const { user } = useAuth();
|
||||
const [activeCategory, setActiveCategory] = useState<string | null>(
|
||||
initialStatus,
|
||||
);
|
||||
|
||||
const pagination = usePagination({
|
||||
fetchFunction: async (page) => {
|
||||
return await apiDonationGetByStatus({
|
||||
authorId: user?.id as string,
|
||||
status: activeCategory as string,
|
||||
page: String(page),
|
||||
});
|
||||
},
|
||||
pageSize: PAGINATION_DEFAULT_TAKE, // Sesuaikan dengan jumlah item per halaman dari API
|
||||
dependencies: [user?.id, activeCategory],
|
||||
});
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
pagination.onRefresh();
|
||||
}, [user?.id, activeCategory]),
|
||||
);
|
||||
|
||||
const handlePress = (item: any) => {
|
||||
setActiveCategory(item.value);
|
||||
pagination.reset();
|
||||
};
|
||||
|
||||
const scrollComponent = (
|
||||
<ScrollableCustom
|
||||
data={dummyMasterStatus.map((e, i) => ({
|
||||
id: i,
|
||||
label: e.label,
|
||||
value: e.value,
|
||||
}))}
|
||||
onButtonPress={handlePress}
|
||||
activeId={activeCategory as any}
|
||||
/>
|
||||
);
|
||||
|
||||
const renderItem = ({ item, index }: { item: any; index: number }) => (
|
||||
<Donasi_BoxStatus data={item} status={activeCategory as string} />
|
||||
);
|
||||
|
||||
const { ListEmptyComponent, ListFooterComponent } =
|
||||
createPaginationComponents({
|
||||
loading: pagination.loading,
|
||||
refreshing: pagination.refreshing,
|
||||
listData: pagination.listData,
|
||||
isInitialLoad: pagination.isInitialLoad,
|
||||
emptyMessage: `Tidak ada data ${activeCategory}`,
|
||||
skeletonCount: 5,
|
||||
skeletonHeight: 120,
|
||||
});
|
||||
|
||||
return (
|
||||
<NewWrapper
|
||||
listData={pagination.listData}
|
||||
renderItem={renderItem}
|
||||
onEndReached={pagination.loadMore}
|
||||
ListEmptyComponent={ListEmptyComponent}
|
||||
ListFooterComponent={ListFooterComponent}
|
||||
refreshControl={
|
||||
<RefreshControl
|
||||
refreshing={pagination.refreshing}
|
||||
onRefresh={pagination.onRefresh}
|
||||
tintColor={MainColor.yellow}
|
||||
colors={[MainColor.yellow]}
|
||||
/>
|
||||
}
|
||||
hideFooter
|
||||
headerComponent={scrollComponent}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -13,8 +13,8 @@ import { useAuth } from "@/hooks/use-auth";
|
||||
import { usePagination } from "@/hooks/use-pagination";
|
||||
import { dummyMasterStatus } from "@/lib/dummy-data/_master/status";
|
||||
import { apiEventGetByStatus } from "@/service/api-client/api-event";
|
||||
import { useLocalSearchParams } from "expo-router";
|
||||
import { useState } from "react";
|
||||
import { useFocusEffect, useLocalSearchParams } from "expo-router";
|
||||
import { useCallback, useState } from "react";
|
||||
import { RefreshControl, View } from "react-native";
|
||||
|
||||
export default function Event_ScreenStatus() {
|
||||
@@ -84,6 +84,12 @@ export default function Event_ScreenStatus() {
|
||||
pagination.reset();
|
||||
};
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
pagination.onRefresh();
|
||||
}, [activeCategory])
|
||||
);
|
||||
|
||||
const tabsComponent = (
|
||||
<ScrollableCustom
|
||||
data={dummyMasterStatus.map((e, i) => ({
|
||||
|
||||
@@ -16,6 +16,9 @@ export default function Investment_ButtonStatusSection({
|
||||
status: string;
|
||||
buttonPublish?: React.ReactNode;
|
||||
}) {
|
||||
const path : any= (status: string) => {
|
||||
return `/investment/(tabs)/portofolio?status=${status}`;
|
||||
};
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const handleBatalkanReview = () => {
|
||||
AlertDefaultSystem({
|
||||
@@ -30,13 +33,13 @@ export default function Investment_ButtonStatusSection({
|
||||
id: id as string,
|
||||
status: "draft",
|
||||
});
|
||||
console.log("[RESPONSE]", JSON.stringify(response, null, 2));
|
||||
|
||||
if (response.success) {
|
||||
Toast.show({
|
||||
type: "success",
|
||||
text1: "Berhasil Batalkan Review",
|
||||
});
|
||||
router.back();
|
||||
router.replace(path("draft"));
|
||||
} else {
|
||||
Toast.show({
|
||||
type: "error",
|
||||
@@ -65,13 +68,13 @@ export default function Investment_ButtonStatusSection({
|
||||
id: id as string,
|
||||
status: "review",
|
||||
});
|
||||
console.log("[RESPONSE]", JSON.stringify(response, null, 2));
|
||||
|
||||
if (response.success) {
|
||||
Toast.show({
|
||||
type: "success",
|
||||
text1: "Berhasil Ajukan Review",
|
||||
});
|
||||
router.back();
|
||||
router.replace(path("review"));
|
||||
} else {
|
||||
Toast.show({
|
||||
type: "error",
|
||||
@@ -100,13 +103,13 @@ export default function Investment_ButtonStatusSection({
|
||||
id: id as string,
|
||||
status: "draft",
|
||||
});
|
||||
console.log("[RESPONSE]", JSON.stringify(response, null, 2));
|
||||
|
||||
if (response.success) {
|
||||
Toast.show({
|
||||
type: "success",
|
||||
text1: "Berhasil Update Status",
|
||||
});
|
||||
router.back();
|
||||
router.replace(path("draft"));
|
||||
} else {
|
||||
Toast.show({
|
||||
type: "error",
|
||||
@@ -135,8 +138,6 @@ export default function Investment_ButtonStatusSection({
|
||||
id: id as string,
|
||||
});
|
||||
|
||||
console.log("[RESPONSE DELETE]", JSON.stringify(response, null, 2));
|
||||
|
||||
if (response.success) {
|
||||
Toast.show({
|
||||
type: "success",
|
||||
|
||||
@@ -16,10 +16,7 @@ export default function Investment_BoxDetailDocument({
|
||||
<Grid>
|
||||
<Grid.Col span={leftIcon ? 10 : 12}>
|
||||
<ClickableCustom onPress={() => router.push(href as any)}>
|
||||
<TextCustom truncate>
|
||||
{title ||
|
||||
`Judul Dokumen: Lorem, ipsum dolor sit amet consectetur adipisicing elit.`}
|
||||
</TextCustom>
|
||||
<TextCustom truncate>{title || "-"}</TextCustom>
|
||||
</ClickableCustom>
|
||||
</Grid.Col>
|
||||
{leftIcon && <Grid.Col span={2}>{leftIcon}</Grid.Col>}
|
||||
|
||||
76
screens/Invesment/Document/ScreenListDocument.tsx
Normal file
76
screens/Invesment/Document/ScreenListDocument.tsx
Normal file
@@ -0,0 +1,76 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import { TextCustom } from "@/components";
|
||||
import NewWrapper from "@/components/_ShareComponent/NewWrapper";
|
||||
import { PAGINATION_DEFAULT_TAKE } from "@/constants/constans-value";
|
||||
import { createPaginationComponents } from "@/helpers/paginationHelpers";
|
||||
import { usePagination } from "@/hooks/use-pagination";
|
||||
import Investment_BoxDetailDocument from "@/screens/Invesment/Document/RecapBoxDetail";
|
||||
import { apiInvestmentGetDocument } from "@/service/api-client/api-investment";
|
||||
import { useFocusEffect, useLocalSearchParams } from "expo-router";
|
||||
import _ from "lodash";
|
||||
import { useCallback } from "react";
|
||||
import { RefreshControl } from "react-native";
|
||||
|
||||
export default function Investment_ScreenListOfDocument() {
|
||||
const { id } = useLocalSearchParams();
|
||||
console.log("ID >> ", id);
|
||||
|
||||
// Setup pagination
|
||||
const pagination = usePagination({
|
||||
fetchFunction: async (page) => {
|
||||
if (!id) return { data: [] };
|
||||
|
||||
return await apiInvestmentGetDocument({
|
||||
id: id as string,
|
||||
category: "all-document",
|
||||
page: String(page),
|
||||
});
|
||||
},
|
||||
pageSize: PAGINATION_DEFAULT_TAKE,
|
||||
dependencies: [id],
|
||||
onError: (error) => console.error("[ERROR] Fetch document:", error),
|
||||
});
|
||||
|
||||
// Generate komponen
|
||||
const { ListEmptyComponent, ListFooterComponent } =
|
||||
createPaginationComponents({
|
||||
loading: pagination.loading,
|
||||
refreshing: pagination.refreshing,
|
||||
listData: pagination.listData,
|
||||
emptyMessage: "Tidak ada dokumen",
|
||||
skeletonCount: PAGINATION_DEFAULT_TAKE,
|
||||
skeletonHeight: 100,
|
||||
});
|
||||
|
||||
// Render item dokumen
|
||||
const renderDocumentItem = ({ item }: { item: any }) => (
|
||||
<Investment_BoxDetailDocument
|
||||
key={item.id}
|
||||
title={item.title}
|
||||
href={`/(file)/${item.fileId}`}
|
||||
/>
|
||||
);
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
pagination.onRefresh();
|
||||
}, [id]),
|
||||
);
|
||||
|
||||
return (
|
||||
<NewWrapper
|
||||
hideFooter
|
||||
listData={pagination.listData}
|
||||
renderItem={renderDocumentItem}
|
||||
refreshControl={
|
||||
<RefreshControl
|
||||
refreshing={pagination.refreshing}
|
||||
onRefresh={pagination.onRefresh}
|
||||
/>
|
||||
}
|
||||
onEndReached={pagination.loadMore}
|
||||
ListEmptyComponent={ListEmptyComponent}
|
||||
ListFooterComponent={ListFooterComponent}
|
||||
/>
|
||||
);
|
||||
}
|
||||
230
screens/Invesment/Document/ScreenRecapOfDocument.tsx
Normal file
230
screens/Invesment/Document/ScreenRecapOfDocument.tsx
Normal file
@@ -0,0 +1,230 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
AlertDefaultSystem,
|
||||
BackButton,
|
||||
DotButton,
|
||||
DrawerCustom,
|
||||
MenuDrawerDynamicGrid
|
||||
} from "@/components";
|
||||
import { IconEdit } from "@/components/_Icon";
|
||||
import NewWrapper from "@/components/_ShareComponent/NewWrapper";
|
||||
import { MainColor } from "@/constants/color-palet";
|
||||
import { ICON_SIZE_SMALL, PAGINATION_DEFAULT_TAKE } from "@/constants/constans-value";
|
||||
import { createPaginationComponents } from "@/helpers/paginationHelpers";
|
||||
import { usePagination } from "@/hooks/use-pagination";
|
||||
import Investment_BoxDetailDocument from "@/screens/Invesment/Document/RecapBoxDetail";
|
||||
import {
|
||||
apiInvestmentDeleteDocument,
|
||||
apiInvestmentGetDocument,
|
||||
} from "@/service/api-client/api-investment";
|
||||
import { AntDesign, Ionicons } from "@expo/vector-icons";
|
||||
import {
|
||||
router,
|
||||
Stack,
|
||||
useFocusEffect,
|
||||
useLocalSearchParams,
|
||||
} from "expo-router";
|
||||
import { useCallback, useState } from "react";
|
||||
import { RefreshControl } from "react-native";
|
||||
import Toast from "react-native-toast-message";
|
||||
|
||||
export default function Investment_ScreenRecapOfDocument() {
|
||||
const { id } = useLocalSearchParams();
|
||||
const [openDrawer, setOpenDrawer] = useState(false);
|
||||
const [openDrawerBox, setOpenDrawerBox] = useState(false);
|
||||
const [selectId, setSelectId] = useState<string | null>(null);
|
||||
|
||||
// Setup pagination
|
||||
const pagination = usePagination({
|
||||
fetchFunction: async (page) => {
|
||||
if (!id) return { data: [] };
|
||||
|
||||
return await apiInvestmentGetDocument({
|
||||
id: id as string,
|
||||
category: "all-document",
|
||||
page: String(page),
|
||||
});
|
||||
},
|
||||
pageSize: PAGINATION_DEFAULT_TAKE,
|
||||
dependencies: [id],
|
||||
onError: (error) => console.error("[ERROR] Fetch document:", error),
|
||||
});
|
||||
|
||||
// Generate komponen
|
||||
const { ListEmptyComponent, ListFooterComponent } =
|
||||
createPaginationComponents({
|
||||
loading: pagination.loading,
|
||||
refreshing: pagination.refreshing,
|
||||
listData: pagination.listData,
|
||||
emptyMessage: "Tidak ada dokumen",
|
||||
skeletonCount: PAGINATION_DEFAULT_TAKE,
|
||||
skeletonHeight: 100,
|
||||
});
|
||||
|
||||
const handlerDeleteDocument = async () => {
|
||||
try {
|
||||
const response = await apiInvestmentDeleteDocument({
|
||||
id: selectId as string,
|
||||
});
|
||||
|
||||
if (response.success) {
|
||||
Toast.show({
|
||||
type: "success",
|
||||
text1: "Data berhasil dihapus",
|
||||
});
|
||||
// Hapus item dari list
|
||||
pagination.setListData((prev: any) => {
|
||||
if (!prev) return null;
|
||||
return prev.filter((item: any) => item.id !== selectId);
|
||||
});
|
||||
|
||||
pagination.onRefresh();
|
||||
setOpenDrawerBox(false);
|
||||
setSelectId(null);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
Toast.show({
|
||||
type: "error",
|
||||
text1: "Gagal menghapus data",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
pagination.onRefresh();
|
||||
}, []),
|
||||
);
|
||||
|
||||
// Render item dokumen
|
||||
const renderDocumentItem = ({ item }: { item: any }) => (
|
||||
<Investment_BoxDetailDocument
|
||||
key={item.id}
|
||||
title={item.title}
|
||||
leftIcon={
|
||||
<Ionicons
|
||||
name="ellipsis-horizontal-outline"
|
||||
size={ICON_SIZE_SMALL}
|
||||
color={MainColor.white}
|
||||
style={{
|
||||
zIndex: 10,
|
||||
alignSelf: "flex-end",
|
||||
}}
|
||||
onPress={() => {
|
||||
setSelectId(item.id);
|
||||
setOpenDrawerBox(true);
|
||||
}}
|
||||
/>
|
||||
}
|
||||
href={`/(file)/${item.fileId}`}
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Stack.Screen
|
||||
options={{
|
||||
title: "Rekap Dokumen",
|
||||
headerLeft: () => <BackButton />,
|
||||
headerRight: () => (
|
||||
<DotButton
|
||||
onPress={() => {
|
||||
setOpenDrawer(true);
|
||||
setOpenDrawerBox(false);
|
||||
}}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
|
||||
<NewWrapper
|
||||
hideFooter
|
||||
listData={pagination.listData}
|
||||
renderItem={renderDocumentItem}
|
||||
refreshControl={
|
||||
<RefreshControl
|
||||
refreshing={pagination.refreshing}
|
||||
onRefresh={pagination.onRefresh}
|
||||
/>
|
||||
}
|
||||
onEndReached={pagination.loadMore}
|
||||
ListEmptyComponent={ListEmptyComponent}
|
||||
ListFooterComponent={ListFooterComponent}
|
||||
/>
|
||||
|
||||
{/* Drawer On Header */}
|
||||
<DrawerCustom
|
||||
isVisible={openDrawer}
|
||||
closeDrawer={() => setOpenDrawer(false)}
|
||||
height={"auto"}
|
||||
>
|
||||
<MenuDrawerDynamicGrid
|
||||
data={[
|
||||
{
|
||||
icon: (
|
||||
<AntDesign
|
||||
name="plus-circle"
|
||||
size={ICON_SIZE_SMALL}
|
||||
color={MainColor.white}
|
||||
/>
|
||||
),
|
||||
label: "Tambah Dokumen",
|
||||
path: `/investment/${id}/(document)/add-document`,
|
||||
},
|
||||
]}
|
||||
onPressItem={(item) => {
|
||||
router.push(item.path as any);
|
||||
setOpenDrawer(false);
|
||||
}}
|
||||
/>
|
||||
</DrawerCustom>
|
||||
|
||||
{/* Drawer On Box */}
|
||||
<DrawerCustom
|
||||
isVisible={openDrawerBox}
|
||||
closeDrawer={() => setOpenDrawerBox(false)}
|
||||
height={"auto"}
|
||||
>
|
||||
<MenuDrawerDynamicGrid
|
||||
data={[
|
||||
{
|
||||
icon: <IconEdit />,
|
||||
label: "Edit Dokumen",
|
||||
path: `/investment/${selectId}/(document)/edit-document`,
|
||||
},
|
||||
{
|
||||
icon: (
|
||||
<Ionicons
|
||||
name="trash-outline"
|
||||
size={ICON_SIZE_SMALL}
|
||||
color={MainColor.white}
|
||||
/>
|
||||
),
|
||||
label: "Hapus Dokumen",
|
||||
path: "" as any,
|
||||
color: MainColor.red,
|
||||
},
|
||||
]}
|
||||
onPressItem={(item) => {
|
||||
if (item.path === ("" as any)) {
|
||||
AlertDefaultSystem({
|
||||
title: "Hapus Dokumen",
|
||||
message: "Apakah anda yakin ingin menghapus dokumen ini?",
|
||||
textLeft: "Batal",
|
||||
textRight: "Hapus",
|
||||
onPressRight: () => {
|
||||
handlerDeleteDocument();
|
||||
},
|
||||
});
|
||||
} else {
|
||||
router.push(item.path as any);
|
||||
}
|
||||
|
||||
setOpenDrawerBox(false);
|
||||
}}
|
||||
/>
|
||||
</DrawerCustom>
|
||||
</>
|
||||
);
|
||||
}
|
||||
13
screens/Invesment/News/BoxNews.tsx
Normal file
13
screens/Invesment/News/BoxNews.tsx
Normal file
@@ -0,0 +1,13 @@
|
||||
import { BaseBox, Spacing, TextCustom } from "@/components";
|
||||
|
||||
export default function Investment_BoxNews({ item }: { item: { id: string; title: string } }) {
|
||||
return (
|
||||
<>
|
||||
<BaseBox paddingBlock={5} href={`/investment/[id]/(news)/${item.id}`}>
|
||||
<Spacing height={10} />
|
||||
<TextCustom bold truncate={2}>{item.title}</TextCustom>
|
||||
<Spacing height={10} />
|
||||
</BaseBox>
|
||||
</>
|
||||
);
|
||||
}
|
||||
109
screens/Invesment/News/ScreenListOfNews.tsx
Normal file
109
screens/Invesment/News/ScreenListOfNews.tsx
Normal file
@@ -0,0 +1,109 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
BackButton,
|
||||
BaseBox,
|
||||
DrawerCustom,
|
||||
LoaderCustom,
|
||||
MenuDrawerDynamicGrid,
|
||||
TextCustom,
|
||||
} from "@/components";
|
||||
import { IconPlus } from "@/components/_Icon";
|
||||
import NewWrapper from "@/components/_ShareComponent/NewWrapper";
|
||||
import { usePagination } from "@/hooks/use-pagination";
|
||||
import { apiInvestmentGetNews } from "@/service/api-client/api-investment";
|
||||
import { router, Stack, useFocusEffect } from "expo-router";
|
||||
import { useCallback, useState } from "react";
|
||||
import { RefreshControl } from "react-native";
|
||||
import { createPaginationComponents } from "@/helpers/paginationHelpers";
|
||||
import Investment_BoxNews from "./BoxNews";
|
||||
|
||||
interface InvestmentListOfNewsProps {
|
||||
investmentId: string;
|
||||
}
|
||||
|
||||
export default function Investment_ScreenListOfNews({
|
||||
investmentId,
|
||||
}: InvestmentListOfNewsProps) {
|
||||
const [openDrawer, setOpenDrawer] = useState(false);
|
||||
|
||||
const pagination = usePagination({
|
||||
fetchFunction: async (page) => {
|
||||
return await apiInvestmentGetNews({
|
||||
id: investmentId,
|
||||
category: "all-news",
|
||||
page: String(page),
|
||||
});
|
||||
},
|
||||
pageSize: 10, // Sesuaikan dengan jumlah item per halaman dari API
|
||||
dependencies: [investmentId],
|
||||
});
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
pagination.onRefresh();
|
||||
}, [investmentId]),
|
||||
);
|
||||
|
||||
const renderItem = ({ item, index }: { item: any; index: number }) => (
|
||||
<Investment_BoxNews key={index} item={item} />
|
||||
);
|
||||
|
||||
const { ListEmptyComponent, ListFooterComponent } =
|
||||
createPaginationComponents({
|
||||
loading: pagination.loading,
|
||||
refreshing: pagination.refreshing,
|
||||
listData: pagination.listData,
|
||||
isInitialLoad: pagination.isInitialLoad,
|
||||
emptyMessage: "Tidak ada berita",
|
||||
skeletonCount: 5,
|
||||
skeletonHeight: 80,
|
||||
loadingFooterText: "Memuat lebih banyak berita...",
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<Stack.Screen
|
||||
options={{
|
||||
title: "Daftar Berita",
|
||||
headerLeft: () => <BackButton />,
|
||||
// headerRight: () => <DotButton onPress={() => setOpenDrawer(true)} />,
|
||||
}}
|
||||
/>
|
||||
|
||||
<NewWrapper
|
||||
hideFooter
|
||||
listData={pagination.listData}
|
||||
renderItem={renderItem}
|
||||
onEndReached={pagination.loadMore}
|
||||
ListEmptyComponent={ListEmptyComponent}
|
||||
ListFooterComponent={ListFooterComponent}
|
||||
refreshControl={
|
||||
<RefreshControl
|
||||
refreshing={pagination.refreshing}
|
||||
onRefresh={pagination.onRefresh}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
|
||||
<DrawerCustom
|
||||
isVisible={openDrawer}
|
||||
closeDrawer={() => setOpenDrawer(false)}
|
||||
height={"auto"}
|
||||
>
|
||||
<MenuDrawerDynamicGrid
|
||||
data={[
|
||||
{
|
||||
label: "Tambah Berita",
|
||||
path: `/investment/${investmentId}/add-news`,
|
||||
icon: <IconPlus />,
|
||||
},
|
||||
]}
|
||||
onPressItem={(item) => {
|
||||
router.push(item.path as any);
|
||||
setOpenDrawer(false);
|
||||
}}
|
||||
/>
|
||||
</DrawerCustom>
|
||||
</>
|
||||
);
|
||||
}
|
||||
110
screens/Invesment/News/ScreenRecapOfNews.tsx
Normal file
110
screens/Invesment/News/ScreenRecapOfNews.tsx
Normal file
@@ -0,0 +1,110 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
BackButton,
|
||||
BaseBox,
|
||||
DotButton,
|
||||
DrawerCustom,
|
||||
LoaderCustom,
|
||||
MenuDrawerDynamicGrid,
|
||||
Spacing,
|
||||
TextCustom,
|
||||
} from "@/components";
|
||||
import { IconPlus } from "@/components/_Icon";
|
||||
import NewWrapper from "@/components/_ShareComponent/NewWrapper";
|
||||
import { usePagination } from "@/hooks/use-pagination";
|
||||
import { apiInvestmentGetNews } from "@/service/api-client/api-investment";
|
||||
import { router, Stack, useFocusEffect } from "expo-router";
|
||||
import { useCallback, useState } from "react";
|
||||
import { RefreshControl } from "react-native";
|
||||
import { createPaginationComponents } from "@/helpers/paginationHelpers";
|
||||
import { PAGINATION_DEFAULT_TAKE } from "@/constants/constans-value";
|
||||
import Investment_BoxNews from "./BoxNews";
|
||||
|
||||
interface InvestmentRecapOfNewsProps {
|
||||
investmentId: string;
|
||||
}
|
||||
|
||||
export default function Investment_ScreenRecapOfNews({
|
||||
investmentId,
|
||||
}: InvestmentRecapOfNewsProps) {
|
||||
const [openDrawer, setOpenDrawer] = useState(false);
|
||||
|
||||
const pagination = usePagination({
|
||||
fetchFunction: async (page) => {
|
||||
return await apiInvestmentGetNews({
|
||||
id: investmentId,
|
||||
category: "all-news",
|
||||
page: String(page),
|
||||
});
|
||||
},
|
||||
pageSize: PAGINATION_DEFAULT_TAKE, // Sesuaikan dengan jumlah item per halaman dari API
|
||||
dependencies: [investmentId],
|
||||
});
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
pagination.onRefresh();
|
||||
}, [investmentId]),
|
||||
);
|
||||
|
||||
const renderItem = ({ item, index }: { item: any; index: number }) => (
|
||||
<Investment_BoxNews key={index} item={item} />
|
||||
);
|
||||
|
||||
const { ListEmptyComponent, ListFooterComponent } =
|
||||
createPaginationComponents({
|
||||
loading: pagination.loading,
|
||||
refreshing: pagination.refreshing,
|
||||
listData: pagination.listData,
|
||||
isInitialLoad: pagination.isInitialLoad,
|
||||
emptyMessage: "Tidak ada berita",
|
||||
skeletonCount: PAGINATION_DEFAULT_TAKE,
|
||||
skeletonHeight: 80,
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<Stack.Screen
|
||||
options={{
|
||||
title: "Rekap Berita",
|
||||
headerLeft: () => <BackButton />,
|
||||
headerRight: () => <DotButton onPress={() => setOpenDrawer(true)} />,
|
||||
}}
|
||||
/>
|
||||
<NewWrapper
|
||||
hideFooter
|
||||
listData={pagination.listData}
|
||||
renderItem={renderItem}
|
||||
onEndReached={pagination.loadMore}
|
||||
ListEmptyComponent={ListEmptyComponent}
|
||||
ListFooterComponent={ListFooterComponent}
|
||||
refreshControl={
|
||||
<RefreshControl
|
||||
refreshing={pagination.refreshing}
|
||||
onRefresh={pagination.onRefresh}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
|
||||
<DrawerCustom
|
||||
isVisible={openDrawer}
|
||||
closeDrawer={() => setOpenDrawer(false)}
|
||||
height={"auto"}
|
||||
>
|
||||
<MenuDrawerDynamicGrid
|
||||
data={[
|
||||
{
|
||||
label: "Tambah Berita",
|
||||
path: `/investment/${investmentId}/add-news`,
|
||||
icon: <IconPlus />,
|
||||
},
|
||||
]}
|
||||
onPressItem={(item) => {
|
||||
router.push(item.path as any);
|
||||
setOpenDrawer(false);
|
||||
}}
|
||||
/>
|
||||
</DrawerCustom>
|
||||
</>
|
||||
);
|
||||
}
|
||||
67
screens/Invesment/ScreenBursa.tsx
Normal file
67
screens/Invesment/ScreenBursa.tsx
Normal file
@@ -0,0 +1,67 @@
|
||||
import {
|
||||
FloatingButton,
|
||||
TextCustom,
|
||||
} from "@/components";
|
||||
import NewWrapper from "@/components/_ShareComponent/NewWrapper";
|
||||
import { PAGINATION_DEFAULT_TAKE } from "@/constants/constans-value";
|
||||
import { createPaginationComponents } from "@/helpers/paginationHelpers";
|
||||
import { usePagination } from "@/hooks/use-pagination";
|
||||
import Investment_BoxBerandaSection from "@/screens/Invesment/BoxBerandaSection";
|
||||
import { apiInvestmentGetAll } from "@/service/api-client/api-investment";
|
||||
import { router } from "expo-router";
|
||||
import _ from "lodash";
|
||||
import { RefreshControl } from "react-native";
|
||||
|
||||
export default function Investment_ScreenBursa() {
|
||||
// Setup pagination
|
||||
const pagination = usePagination({
|
||||
fetchFunction: async (page) => {
|
||||
return await apiInvestmentGetAll({
|
||||
category: "bursa",
|
||||
page: String(page),
|
||||
});
|
||||
},
|
||||
pageSize: PAGINATION_DEFAULT_TAKE,
|
||||
dependencies: [],
|
||||
onError: (error) => console.error("[ERROR] Fetch investment bursa:", error),
|
||||
});
|
||||
|
||||
// Generate komponen
|
||||
const { ListEmptyComponent, ListFooterComponent } = createPaginationComponents({
|
||||
loading: pagination.loading,
|
||||
refreshing: pagination.refreshing,
|
||||
listData: pagination.listData,
|
||||
emptyMessage: "Belum ada investasi",
|
||||
skeletonCount: PAGINATION_DEFAULT_TAKE,
|
||||
skeletonHeight: 100,
|
||||
});
|
||||
|
||||
// Render item investment
|
||||
const renderInvestmentItem = ({ item }: { item: any }) => (
|
||||
<Investment_BoxBerandaSection
|
||||
key={item.id}
|
||||
id={item.id}
|
||||
data={item}
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<NewWrapper
|
||||
listData={pagination.listData}
|
||||
renderItem={renderInvestmentItem}
|
||||
refreshControl={
|
||||
<RefreshControl
|
||||
refreshing={pagination.refreshing}
|
||||
onRefresh={pagination.onRefresh}
|
||||
/>
|
||||
}
|
||||
onEndReached={pagination.loadMore}
|
||||
ListEmptyComponent={ListEmptyComponent}
|
||||
ListFooterComponent={ListFooterComponent}
|
||||
hideFooter
|
||||
floatingButton={
|
||||
<FloatingButton onPress={() => router.push("/investment/create")} />
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
84
screens/Invesment/ScreenInvestor.tsx
Normal file
84
screens/Invesment/ScreenInvestor.tsx
Normal file
@@ -0,0 +1,84 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
AvatarUsernameAndOtherComponent,
|
||||
BoxWithHeaderSection,
|
||||
CenterCustom,
|
||||
Spacing,
|
||||
StackCustom,
|
||||
TextCustom,
|
||||
} from "@/components";
|
||||
import NewWrapper from "@/components/_ShareComponent/NewWrapper";
|
||||
import { PAGINATION_DEFAULT_TAKE } from "@/constants/constans-value";
|
||||
import { createPaginationComponents } from "@/helpers/paginationHelpers";
|
||||
import { usePagination } from "@/hooks/use-pagination";
|
||||
import { apiInvestmentGetInvestorById } from "@/service/api-client/api-investment";
|
||||
import { formatCurrencyDisplay } from "@/utils/formatCurrencyDisplay";
|
||||
import { RefreshControl } from "react-native";
|
||||
|
||||
interface InvestmentInvestorProps {
|
||||
investmentId: string;
|
||||
}
|
||||
|
||||
export default function Investment_ScreenInvestor({
|
||||
investmentId,
|
||||
}: InvestmentInvestorProps) {
|
||||
const pagination = usePagination({
|
||||
fetchFunction: async (page) => {
|
||||
return await apiInvestmentGetInvestorById({
|
||||
id: investmentId,
|
||||
page: String(page),
|
||||
});
|
||||
},
|
||||
pageSize: PAGINATION_DEFAULT_TAKE,
|
||||
dependencies: [investmentId],
|
||||
onError: (error) => {
|
||||
console.error("Error fetching investors:", error);
|
||||
},
|
||||
});
|
||||
|
||||
const renderItem = ({ item }: { item: any }) => (
|
||||
<BoxWithHeaderSection>
|
||||
<StackCustom>
|
||||
<AvatarUsernameAndOtherComponent
|
||||
avatar={item?.Author?.Profile?.imageId}
|
||||
name={item?.Author?.username}
|
||||
avatarHref={`/profile/${item?.Author?.Profile?.id}`}
|
||||
/>
|
||||
<CenterCustom>
|
||||
<TextCustom size="large" bold>
|
||||
Rp. {formatCurrencyDisplay(item?.nominal)}
|
||||
</TextCustom>
|
||||
</CenterCustom>
|
||||
</StackCustom>
|
||||
<Spacing height={10} />
|
||||
</BoxWithHeaderSection>
|
||||
);
|
||||
|
||||
const { ListEmptyComponent, ListFooterComponent } =
|
||||
createPaginationComponents({
|
||||
loading: pagination.loading,
|
||||
refreshing: pagination.refreshing,
|
||||
listData: pagination.listData,
|
||||
isInitialLoad: pagination.isInitialLoad,
|
||||
emptyMessage: "Tidak ada investor",
|
||||
skeletonCount: PAGINATION_DEFAULT_TAKE,
|
||||
skeletonHeight: 120,
|
||||
});
|
||||
|
||||
return (
|
||||
<NewWrapper
|
||||
hideFooter
|
||||
listData={pagination.listData}
|
||||
renderItem={renderItem}
|
||||
onEndReached={pagination.loadMore}
|
||||
ListEmptyComponent={ListEmptyComponent}
|
||||
ListFooterComponent={ListFooterComponent}
|
||||
refreshControl={
|
||||
<RefreshControl
|
||||
refreshing={pagination.refreshing}
|
||||
onRefresh={pagination.onRefresh}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
230
screens/Invesment/ScreenInvoice.tsx
Normal file
230
screens/Invesment/ScreenInvoice.tsx
Normal file
@@ -0,0 +1,230 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
BaseBox,
|
||||
ButtonCenteredOnly,
|
||||
ButtonCustom,
|
||||
Grid,
|
||||
InformationBox,
|
||||
Spacing,
|
||||
StackCustom,
|
||||
TextCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import CopyButton from "@/components/Button/CoyButton";
|
||||
import { MainColor } from "@/constants/color-palet";
|
||||
import DIRECTORY_ID from "@/constants/directory-id";
|
||||
import {
|
||||
apiInvestmentGetInvoice,
|
||||
apiInvestmentUpdateInvoice,
|
||||
} from "@/service/api-client/api-investment";
|
||||
import { uploadFileService } from "@/service/upload-service";
|
||||
import { formatCurrencyDisplay } from "@/utils/formatCurrencyDisplay";
|
||||
import pickFile, { IFileData } from "@/utils/pickFile";
|
||||
import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
|
||||
import { useCallback, useState } from "react";
|
||||
import { View } from "react-native";
|
||||
import Toast from "react-native-toast-message";
|
||||
|
||||
export default function Investment_ScreenInvoice() {
|
||||
const { id } = useLocalSearchParams();
|
||||
const [data, setData] = useState<any>({});
|
||||
const [image, setImage] = useState<IFileData>({
|
||||
name: "",
|
||||
uri: "",
|
||||
size: 0,
|
||||
});
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
onLoadData();
|
||||
}, [id])
|
||||
);
|
||||
|
||||
const onLoadData = async () => {
|
||||
try {
|
||||
const response = await apiInvestmentGetInvoice({
|
||||
id: id as string,
|
||||
category: "invoice",
|
||||
});
|
||||
|
||||
setData(response.data);
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
}
|
||||
};
|
||||
|
||||
const handlerSubmitUpdate = async () => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
const responseUploadImage = await uploadFileService({
|
||||
dirId: DIRECTORY_ID.investasi_bukti_transfer,
|
||||
imageUri: image?.uri,
|
||||
});
|
||||
|
||||
if (!responseUploadImage?.data?.id) {
|
||||
Toast.show({
|
||||
type: "error",
|
||||
text1: "Gagal mengunggah bukti transfer",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const response = await apiInvestmentUpdateInvoice({
|
||||
id: id as string,
|
||||
data: {
|
||||
imageId: responseUploadImage?.data?.id,
|
||||
},
|
||||
status: "proses",
|
||||
});
|
||||
|
||||
if (response.success) {
|
||||
Toast.show({
|
||||
type: "success",
|
||||
text1: "Berhasil mengunggah bukti transfer",
|
||||
});
|
||||
router.push(`/investment/${id}/(transaction-flow)/process`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<ViewWrapper>
|
||||
<StackCustom>
|
||||
<InformationBox text="Mohon transfer ke rekening dibawah" />
|
||||
<BaseBox>
|
||||
<StackCustom gap={"xs"}>
|
||||
<Grid>
|
||||
<Grid.Col span={4}>
|
||||
<TextCustom>Bank</TextCustom>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={8}>
|
||||
<TextCustom>{data?.MasterBank?.namaBank}</TextCustom>
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
<Spacing height={10} />
|
||||
<Grid>
|
||||
<Grid.Col span={4}>
|
||||
<TextCustom>Nama Akun</TextCustom>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={8}>
|
||||
<TextCustom>{data?.MasterBank?.namaAkun}</TextCustom>
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
|
||||
<BaseBox backgroundColor={MainColor.soft_darkblue}>
|
||||
<Grid containerStyle={{ justifyContent: "center" }}>
|
||||
<Grid.Col
|
||||
span={8}
|
||||
style={{
|
||||
justifyContent: "center",
|
||||
}}
|
||||
>
|
||||
<TextCustom size="xlarge" bold color="yellow">
|
||||
{data?.MasterBank?.norek}
|
||||
</TextCustom>
|
||||
</Grid.Col>
|
||||
<Grid.Col
|
||||
span={4}
|
||||
style={{
|
||||
alignItems: "flex-end",
|
||||
}}
|
||||
>
|
||||
<CopyButton textToCopy={data?.MasterBank?.norek} />
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
</BaseBox>
|
||||
</StackCustom>
|
||||
</BaseBox>
|
||||
|
||||
<BaseBox>
|
||||
<StackCustom gap={"xs"}>
|
||||
<TextCustom>Jumlah Transaksi</TextCustom>
|
||||
|
||||
<Spacing height={10} />
|
||||
|
||||
<BaseBox backgroundColor={MainColor.soft_darkblue}>
|
||||
<Grid containerStyle={{ justifyContent: "center" }}>
|
||||
<Grid.Col
|
||||
span={8}
|
||||
style={{
|
||||
justifyContent: "center",
|
||||
}}
|
||||
>
|
||||
<TextCustom size="xlarge" bold color="yellow">
|
||||
Rp. {formatCurrencyDisplay(data?.nominal)}
|
||||
</TextCustom>
|
||||
</Grid.Col>
|
||||
<Grid.Col
|
||||
span={4}
|
||||
style={{
|
||||
alignItems: "flex-end",
|
||||
}}
|
||||
>
|
||||
<CopyButton textToCopy={data?.nominal} />
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
</BaseBox>
|
||||
</StackCustom>
|
||||
</BaseBox>
|
||||
|
||||
<BaseBox>
|
||||
<StackCustom>
|
||||
<TextCustom align="center">
|
||||
Upload bukti transfer anda.
|
||||
</TextCustom>
|
||||
{image ? (
|
||||
<View
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
gap: 10,
|
||||
paddingInline: 20,
|
||||
}}
|
||||
>
|
||||
<TextCustom bold align="center" truncate>
|
||||
{image?.name}
|
||||
</TextCustom>
|
||||
</View>
|
||||
) : (
|
||||
<TextCustom align="center">
|
||||
Tidak ada gambar yang diunggah
|
||||
</TextCustom>
|
||||
)}
|
||||
<ButtonCenteredOnly
|
||||
onPress={() => {
|
||||
pickFile({
|
||||
allowedType: "image",
|
||||
setImageUri(file: any) {
|
||||
setImage(file);
|
||||
},
|
||||
});
|
||||
}}
|
||||
icon="upload"
|
||||
>
|
||||
Upload
|
||||
</ButtonCenteredOnly>
|
||||
</StackCustom>
|
||||
</BaseBox>
|
||||
|
||||
<ButtonCustom
|
||||
isLoading={isLoading}
|
||||
disabled={!image || isLoading}
|
||||
onPress={() => {
|
||||
handlerSubmitUpdate();
|
||||
}}
|
||||
>
|
||||
Saya Sudah Transfer
|
||||
</ButtonCustom>
|
||||
</StackCustom>
|
||||
<Spacing />
|
||||
</ViewWrapper>
|
||||
</>
|
||||
);
|
||||
}
|
||||
91
screens/Invesment/ScreenMyHolding.tsx
Normal file
91
screens/Invesment/ScreenMyHolding.tsx
Normal file
@@ -0,0 +1,91 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
BaseBox,
|
||||
ProgressCustom,
|
||||
StackCustom,
|
||||
TextCustom
|
||||
} from "@/components";
|
||||
import NewWrapper from "@/components/_ShareComponent/NewWrapper";
|
||||
import { PAGINATION_DEFAULT_TAKE } from "@/constants/constans-value";
|
||||
import { createPaginationComponents } from "@/helpers/paginationHelpers";
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
import { usePagination } from "@/hooks/use-pagination";
|
||||
import { apiInvestmentGetAll } from "@/service/api-client/api-investment";
|
||||
import { formatCurrencyDisplay } from "@/utils/formatCurrencyDisplay";
|
||||
import { router, useFocusEffect } from "expo-router";
|
||||
import { useCallback } from "react";
|
||||
import { RefreshControl } from "react-native";
|
||||
|
||||
export default function Investment_ScreenMyHolding() {
|
||||
const { user } = useAuth();
|
||||
|
||||
const pagination = usePagination({
|
||||
fetchFunction: async (page) => {
|
||||
return await apiInvestmentGetAll({
|
||||
category: "my-holding",
|
||||
authorId: user?.id,
|
||||
page: String(page),
|
||||
});
|
||||
},
|
||||
pageSize: PAGINATION_DEFAULT_TAKE,
|
||||
dependencies: [user?.id],
|
||||
onError: (error) => console.error("[ERROR] Fetch my holding:", error),
|
||||
});
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
pagination.onRefresh();
|
||||
}, [user?.id]),
|
||||
);
|
||||
|
||||
const renderItem = ({ item, index }: { item: any; index: number }) => (
|
||||
<BaseBox
|
||||
paddingTop={7}
|
||||
paddingBottom={7}
|
||||
onPress={() =>
|
||||
router.push(`/investment/${item?.id}/(my-holding)/${item?.id}`)
|
||||
}
|
||||
>
|
||||
<StackCustom>
|
||||
<TextCustom truncate={2}>{item?.title}</TextCustom>
|
||||
<TextCustom>Rp. {formatCurrencyDisplay(item?.nominal)}</TextCustom>
|
||||
<TextCustom>{item?.lembarTerbeli} Lembar</TextCustom>
|
||||
<ProgressCustom
|
||||
label={`${item.progress}%`}
|
||||
value={Number(item.progress)}
|
||||
size="lg"
|
||||
animated
|
||||
color="primary"
|
||||
/>
|
||||
</StackCustom>
|
||||
</BaseBox>
|
||||
);
|
||||
|
||||
const { ListEmptyComponent, ListFooterComponent } =
|
||||
createPaginationComponents({
|
||||
loading: pagination.loading,
|
||||
refreshing: pagination.refreshing,
|
||||
listData: pagination.listData,
|
||||
isInitialLoad: pagination.isInitialLoad,
|
||||
emptyMessage: "Tidak ada investasi yang dimiliki",
|
||||
skeletonCount: PAGINATION_DEFAULT_TAKE,
|
||||
skeletonHeight: 120,
|
||||
});
|
||||
|
||||
return (
|
||||
<NewWrapper
|
||||
listData={pagination.listData}
|
||||
renderItem={renderItem}
|
||||
onEndReached={pagination.loadMore}
|
||||
ListEmptyComponent={ListEmptyComponent}
|
||||
ListFooterComponent={ListFooterComponent}
|
||||
refreshControl={
|
||||
<RefreshControl
|
||||
refreshing={pagination.refreshing}
|
||||
onRefresh={pagination.onRefresh}
|
||||
/>
|
||||
}
|
||||
hideFooter
|
||||
/>
|
||||
);
|
||||
}
|
||||
102
screens/Invesment/ScreenPortofolio.tsx
Normal file
102
screens/Invesment/ScreenPortofolio.tsx
Normal file
@@ -0,0 +1,102 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import { ScrollableCustom, TextCustom } from "@/components";
|
||||
import NewWrapper from "@/components/_ShareComponent/NewWrapper";
|
||||
import { PAGINATION_DEFAULT_TAKE } from "@/constants/constans-value";
|
||||
import { createPaginationComponents } from "@/helpers/paginationHelpers";
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
import { usePagination } from "@/hooks/use-pagination";
|
||||
import { dummyMasterStatus } from "@/lib/dummy-data/_master/status";
|
||||
import Investment_StatusBox from "@/screens/Invesment/StatusBox";
|
||||
import { apiInvestmentGetByStatus } from "@/service/api-client/api-investment";
|
||||
import { useFocusEffect, useLocalSearchParams } from "expo-router";
|
||||
import { useCallback, useState } from "react";
|
||||
import { RefreshControl } from "react-native";
|
||||
|
||||
export default function Investment_ScreenPortofolio() {
|
||||
const { user } = useAuth();
|
||||
const { status } = useLocalSearchParams<{ status?: string }>();
|
||||
|
||||
const [activeCategory, setActiveCategory] = useState<string | null>(
|
||||
status || "publish",
|
||||
);
|
||||
|
||||
// Setup pagination
|
||||
const pagination = usePagination({
|
||||
fetchFunction: async (page) => {
|
||||
if (!user?.id) return { data: [] };
|
||||
|
||||
return await apiInvestmentGetByStatus({
|
||||
authorId: user.id,
|
||||
status: activeCategory!,
|
||||
page: String(page),
|
||||
});
|
||||
},
|
||||
pageSize: PAGINATION_DEFAULT_TAKE,
|
||||
dependencies: [user?.id, activeCategory],
|
||||
onError: (error) =>
|
||||
console.error("[ERROR] Fetch investment by status:", error),
|
||||
});
|
||||
|
||||
// Generate komponen
|
||||
const { ListEmptyComponent, ListFooterComponent } =
|
||||
createPaginationComponents({
|
||||
loading: pagination.loading,
|
||||
refreshing: pagination.refreshing,
|
||||
listData: pagination.listData,
|
||||
emptyMessage: `Tidak ada data ${activeCategory}`,
|
||||
skeletonCount: PAGINATION_DEFAULT_TAKE,
|
||||
skeletonHeight: 150,
|
||||
});
|
||||
|
||||
// Render item investment
|
||||
const renderInvestmentItem = ({ item }: { item: any }) => (
|
||||
<Investment_StatusBox
|
||||
key={item.id}
|
||||
data={item}
|
||||
status={activeCategory as string}
|
||||
href={`/investment/${item.id}/${activeCategory}/detail`}
|
||||
/>
|
||||
);
|
||||
|
||||
const handlePress = (item: any) => {
|
||||
setActiveCategory(item.value);
|
||||
// Reset pagination saat kategori berubah
|
||||
pagination.reset();
|
||||
};
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
pagination.onRefresh();
|
||||
}, [activeCategory]),
|
||||
);
|
||||
|
||||
const tabsComponent = (
|
||||
<ScrollableCustom
|
||||
data={dummyMasterStatus.map((e, i) => ({
|
||||
id: i,
|
||||
label: e.label,
|
||||
value: e.value,
|
||||
}))}
|
||||
onButtonPress={handlePress}
|
||||
activeId={activeCategory as any}
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<NewWrapper
|
||||
hideFooter
|
||||
headerComponent={tabsComponent}
|
||||
listData={pagination.listData}
|
||||
renderItem={renderInvestmentItem}
|
||||
refreshControl={
|
||||
<RefreshControl
|
||||
refreshing={pagination.refreshing}
|
||||
onRefresh={pagination.onRefresh}
|
||||
/>
|
||||
}
|
||||
onEndReached={pagination.loadMore}
|
||||
ListEmptyComponent={ListEmptyComponent}
|
||||
ListFooterComponent={ListFooterComponent}
|
||||
/>
|
||||
);
|
||||
}
|
||||
146
screens/Invesment/ScreenTransaction.tsx
Normal file
146
screens/Invesment/ScreenTransaction.tsx
Normal file
@@ -0,0 +1,146 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
BadgeCustom,
|
||||
BaseBox,
|
||||
Grid,
|
||||
Spacing,
|
||||
StackCustom,
|
||||
TextCustom,
|
||||
} from "@/components";
|
||||
import NewWrapper from "@/components/_ShareComponent/NewWrapper";
|
||||
import NoDataText from "@/components/_ShareComponent/NoDataText";
|
||||
import { PAGINATION_DEFAULT_TAKE } from "@/constants/constans-value";
|
||||
import { createPaginationComponents } from "@/helpers/paginationHelpers";
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
import { usePagination } from "@/hooks/use-pagination";
|
||||
import { apiInvestmentGetInvoice } from "@/service/api-client/api-investment";
|
||||
import { GStyles } from "@/styles/global-styles";
|
||||
import { formatChatTime } from "@/utils/formatChatTime";
|
||||
import { formatCurrencyDisplay } from "@/utils/formatCurrencyDisplay";
|
||||
import { router, useFocusEffect } from "expo-router";
|
||||
import _ from "lodash";
|
||||
import { useCallback } from "react";
|
||||
import { RefreshControl, View } from "react-native";
|
||||
|
||||
export default function Investment_ScreenTransaction() {
|
||||
const { user } = useAuth();
|
||||
|
||||
// Setup pagination
|
||||
const pagination = usePagination({
|
||||
fetchFunction: async (page) => {
|
||||
if (!user?.id) return { data: [] };
|
||||
|
||||
return await apiInvestmentGetInvoice({
|
||||
authorId: user.id,
|
||||
category: "transaction",
|
||||
page: String(page),
|
||||
});
|
||||
},
|
||||
pageSize: PAGINATION_DEFAULT_TAKE,
|
||||
dependencies: [user?.id],
|
||||
onError: (error) => console.error("[ERROR] Fetch transaction:", error),
|
||||
});
|
||||
|
||||
// Generate komponen
|
||||
const { ListEmptyComponent, ListFooterComponent } =
|
||||
createPaginationComponents({
|
||||
loading: pagination.loading,
|
||||
refreshing: pagination.refreshing,
|
||||
listData: pagination.listData,
|
||||
emptyMessage: "Tidak ada transaksi",
|
||||
skeletonCount: PAGINATION_DEFAULT_TAKE,
|
||||
skeletonHeight: 100,
|
||||
});
|
||||
|
||||
const handlerColor = (status: string) => {
|
||||
if (status === "menunggu") {
|
||||
return "orange";
|
||||
} else if (status === "proses") {
|
||||
return "white";
|
||||
} else if (status === "berhasil") {
|
||||
return "green";
|
||||
} else if (status === "gagal") {
|
||||
return "red";
|
||||
}
|
||||
};
|
||||
|
||||
const handlePress = ({ id, status }: { id: string; status: string }) => {
|
||||
console.log("ID", id);
|
||||
console.log("Status", status);
|
||||
if (status === "menunggu") {
|
||||
router.push(`/investment/${id}/(transaction-flow)/invoice`);
|
||||
} else if (status === "proses") {
|
||||
router.push(`/investment/${id}/(transaction-flow)/process`);
|
||||
} else if (status === "berhasil") {
|
||||
router.push(`/investment/${id}/(transaction-flow)/success`);
|
||||
} else if (status === "gagal") {
|
||||
router.push(`/investment/${id}/(transaction-flow)/failed`);
|
||||
}
|
||||
};
|
||||
|
||||
// Render item transaksi
|
||||
const renderTransactionItem = ({ item }: { item: any }) => (
|
||||
<BaseBox
|
||||
key={item.id}
|
||||
paddingTop={7}
|
||||
paddingBottom={7}
|
||||
onPress={() => {
|
||||
handlePress({
|
||||
id: item.id,
|
||||
status: _.lowerCase(item.statusInvoice),
|
||||
});
|
||||
}}
|
||||
>
|
||||
<Grid>
|
||||
<Grid.Col span={6}>
|
||||
<StackCustom gap={"xs"}>
|
||||
<TextCustom truncate>{item?.title || "-"}</TextCustom>
|
||||
<TextCustom color="gray" size="small">
|
||||
{formatChatTime(item?.createdAt)}
|
||||
</TextCustom>
|
||||
</StackCustom>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={1}>
|
||||
<View />
|
||||
</Grid.Col>
|
||||
<Grid.Col span={5} style={{ alignItems: "flex-end" }}>
|
||||
<StackCustom gap={"xs"}>
|
||||
<TextCustom bold truncate>
|
||||
Rp. {formatCurrencyDisplay(item?.nominal) || "-"}
|
||||
</TextCustom>
|
||||
<BadgeCustom
|
||||
variant="light"
|
||||
color={handlerColor(_.lowerCase(item.statusInvoice))}
|
||||
style={GStyles.alignSelfFlexEnd}
|
||||
>
|
||||
{item?.statusInvoice || "-"}
|
||||
</BadgeCustom>
|
||||
</StackCustom>
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
</BaseBox>
|
||||
);
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
pagination.onRefresh();
|
||||
}, [user?.id])
|
||||
);
|
||||
|
||||
return (
|
||||
<NewWrapper
|
||||
hideFooter
|
||||
listData={pagination.listData}
|
||||
renderItem={renderTransactionItem}
|
||||
refreshControl={
|
||||
<RefreshControl
|
||||
refreshing={pagination.refreshing}
|
||||
onRefresh={pagination.onRefresh}
|
||||
/>
|
||||
}
|
||||
onEndReached={pagination.loadMore}
|
||||
ListEmptyComponent={ListEmptyComponent}
|
||||
ListFooterComponent={ListFooterComponent}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -20,6 +20,9 @@ export default function Job_ButtonStatusSection({
|
||||
onSetLoading: (value: boolean) => void;
|
||||
isArchive?: boolean;
|
||||
}) {
|
||||
const path : any =(status: string) => {
|
||||
return `/job/(tabs)/status?status=${status}`
|
||||
}
|
||||
const handleBatalkanReview = () => {
|
||||
AlertDefaultSystem({
|
||||
title: "Batalkan Review",
|
||||
@@ -39,7 +42,7 @@ export default function Job_ButtonStatusSection({
|
||||
type: "success",
|
||||
text1: response.message,
|
||||
});
|
||||
router.back();
|
||||
router.replace(path("draft"));
|
||||
} else {
|
||||
Toast.show({
|
||||
type: "info",
|
||||
@@ -76,14 +79,14 @@ export default function Job_ButtonStatusSection({
|
||||
type: "success",
|
||||
text1: response.message,
|
||||
});
|
||||
router.back();
|
||||
router.replace(path("review"));
|
||||
} else {
|
||||
Toast.show({
|
||||
type: "info",
|
||||
text1: "Info",
|
||||
text2: response.message,
|
||||
});
|
||||
router.back();
|
||||
router.back()
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
@@ -113,14 +116,14 @@ export default function Job_ButtonStatusSection({
|
||||
type: "success",
|
||||
text1: response.message,
|
||||
});
|
||||
router.back();
|
||||
router.replace(path("draft"));
|
||||
} else {
|
||||
Toast.show({
|
||||
type: "info",
|
||||
text1: "Info",
|
||||
text2: response.message,
|
||||
});
|
||||
router.back();
|
||||
router. back();
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
|
||||
@@ -62,19 +62,17 @@ export default function Job_MainViewStatus2() {
|
||||
</BaseBox>
|
||||
);
|
||||
|
||||
// useFocusEffect(
|
||||
// useCallback(() => {
|
||||
// // Reset and load first page when category changes
|
||||
// pagination.reset();
|
||||
// // pagination.onRefresh();
|
||||
// }, [activeCategory]),
|
||||
// );
|
||||
|
||||
const handlePress = (item: any) => {
|
||||
setActiveCategory(item.value);
|
||||
// Reset pagination saat kategori berubah
|
||||
pagination.reset();
|
||||
};
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
pagination.onRefresh();
|
||||
}, [activeCategory])
|
||||
);
|
||||
|
||||
const scrollComponent = (
|
||||
<ScrollableCustom
|
||||
|
||||
@@ -52,6 +52,9 @@ const fixPath = ({
|
||||
return deepLink;
|
||||
}
|
||||
|
||||
console.log("Category App", categoryApp);
|
||||
console.log("Deep Link", deepLink);
|
||||
|
||||
const separator = deepLink.includes("?") ? "&" : "?";
|
||||
|
||||
const fixedPath = `${deepLink}${separator}from=notifications&category=${_.lowerCase(
|
||||
@@ -83,8 +86,8 @@ const BoxNotification = ({
|
||||
categoryApp: data.kategoriApp,
|
||||
});
|
||||
|
||||
router.navigate(newPath as any);
|
||||
selectedCategory(activeCategory as string);
|
||||
router.navigate(newPath as any);
|
||||
|
||||
if (!data.isRead) {
|
||||
markAsRead(data.id);
|
||||
@@ -112,7 +115,7 @@ const BoxNotification = ({
|
||||
);
|
||||
};
|
||||
|
||||
export default function ScreenNotification() {
|
||||
export default function ScreenNotification_V2() {
|
||||
const { user } = useAuth();
|
||||
const { category } = useLocalSearchParams<{ category?: string }>();
|
||||
const [activeCategory, setActiveCategory] = useState<string | null>(
|
||||
@@ -140,7 +143,7 @@ export default function ScreenNotification() {
|
||||
// useFocusEffect(
|
||||
// useCallback(() => {
|
||||
// // Reset and load first page when category changes
|
||||
// pagination.reset();
|
||||
|
||||
// pagination.onRefresh();
|
||||
// }, [activeCategory]),
|
||||
// );
|
||||
@@ -171,7 +174,7 @@ export default function ScreenNotification() {
|
||||
listData: pagination.listData,
|
||||
isInitialLoad: pagination.isInitialLoad,
|
||||
emptyMessage: "Belum ada notifikasi",
|
||||
skeletonCount: 5,
|
||||
skeletonCount: PAGINATION_DEFAULT_TAKE,
|
||||
skeletonHeight: 100,
|
||||
});
|
||||
|
||||
@@ -237,7 +240,6 @@ export default function ScreenNotification() {
|
||||
},
|
||||
]}
|
||||
onPressItem={(item: any) => {
|
||||
// console.log("Item", item.value);
|
||||
if (item.value === "read-all") {
|
||||
AlertDefaultSystem({
|
||||
title: "Tandai Semua Dibaca",
|
||||
|
||||
@@ -3,7 +3,7 @@ import {
|
||||
apiVotingDelete,
|
||||
apiVotingUpdateStatus,
|
||||
} from "@/service/api-client/api-voting";
|
||||
import { router } from "expo-router";
|
||||
import { RelativePathString, router } from "expo-router";
|
||||
import Toast from "react-native-toast-message";
|
||||
|
||||
export default function Voting_ButtonStatusSection({
|
||||
@@ -17,6 +17,10 @@ export default function Voting_ButtonStatusSection({
|
||||
isLoading: boolean;
|
||||
onSetLoading: (value: boolean) => void;
|
||||
}) {
|
||||
const path: any = (status: string) => {
|
||||
return `/voting/(tabs)/status?status=${status}`;
|
||||
};
|
||||
|
||||
const handleBatalkanReview = () => {
|
||||
AlertDefaultSystem({
|
||||
title: "Batalkan Review",
|
||||
@@ -34,9 +38,9 @@ export default function Voting_ButtonStatusSection({
|
||||
if (response?.success) {
|
||||
Toast.show({
|
||||
type: "success",
|
||||
text1: response.message,
|
||||
text1: "Berhasil batalkan review",
|
||||
});
|
||||
router.replace(`/voting/${id}/draft/detail`);
|
||||
router.replace(path("draft"));
|
||||
} else {
|
||||
Toast.show({
|
||||
type: "info",
|
||||
@@ -71,9 +75,9 @@ export default function Voting_ButtonStatusSection({
|
||||
if (response?.success) {
|
||||
Toast.show({
|
||||
type: "success",
|
||||
text1: response.message,
|
||||
text1: "Berhasil ajukan review",
|
||||
});
|
||||
router.replace(`/voting/${id}/review/detail`);
|
||||
router.replace(path("review"));
|
||||
} else {
|
||||
Toast.show({
|
||||
type: "info",
|
||||
@@ -108,9 +112,9 @@ export default function Voting_ButtonStatusSection({
|
||||
if (response?.success) {
|
||||
Toast.show({
|
||||
type: "success",
|
||||
text1: response.message,
|
||||
text1: "Berhasil edit kembali",
|
||||
});
|
||||
router.replace(`/voting/${id}/draft/detail`);
|
||||
router.replace(path("draft"));
|
||||
} else {
|
||||
Toast.show({
|
||||
type: "info",
|
||||
@@ -135,31 +139,31 @@ export default function Voting_ButtonStatusSection({
|
||||
textLeft: "Batal",
|
||||
textRight: "Hapus",
|
||||
onPressRight: async () => {
|
||||
try {
|
||||
onSetLoading(true);
|
||||
const response = await apiVotingDelete({
|
||||
id: id as string,
|
||||
});
|
||||
try {
|
||||
onSetLoading(true);
|
||||
const response = await apiVotingDelete({
|
||||
id: id as string,
|
||||
});
|
||||
|
||||
if (response?.success) {
|
||||
Toast.show({
|
||||
type: "success",
|
||||
text1: response.message,
|
||||
});
|
||||
router.back();
|
||||
} else {
|
||||
Toast.show({
|
||||
type: "info",
|
||||
text1: "Info",
|
||||
text2: response.message,
|
||||
});
|
||||
router.back();
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
} finally {
|
||||
onSetLoading(false);
|
||||
}
|
||||
if (response?.success) {
|
||||
Toast.show({
|
||||
type: "success",
|
||||
text1: "Berhasil hapus data",
|
||||
});
|
||||
router.back();
|
||||
} else {
|
||||
Toast.show({
|
||||
type: "info",
|
||||
text1: "Info",
|
||||
text2: response.message,
|
||||
});
|
||||
router.back();
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
} finally {
|
||||
onSetLoading(false);
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
82
screens/Voting/ScreenBeranda.tsx
Normal file
82
screens/Voting/ScreenBeranda.tsx
Normal file
@@ -0,0 +1,82 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import { FloatingButton, SearchInput } from "@/components";
|
||||
import NewWrapper from "@/components/_ShareComponent/NewWrapper";
|
||||
import { createPaginationComponents } from "@/helpers/paginationHelpers";
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
import { usePagination } from "@/hooks/use-pagination";
|
||||
import Voting_BoxPublishSection from "@/screens/Voting/BoxPublishSection";
|
||||
import { apiVotingGetAll } from "@/service/api-client/api-voting";
|
||||
import { router } from "expo-router";
|
||||
import { useMemo, useState } from "react";
|
||||
import { RefreshControl } from "react-native";
|
||||
|
||||
export default function Voting_ScreenBeranda() {
|
||||
const { user } = useAuth();
|
||||
const [search, setSearch] = useState("");
|
||||
|
||||
const pagination = usePagination({
|
||||
fetchFunction: async (page, searchQuery) => {
|
||||
return await apiVotingGetAll({
|
||||
search: searchQuery || "",
|
||||
category: "beranda",
|
||||
userLoginId: user?.id,
|
||||
page: String(page),
|
||||
});
|
||||
},
|
||||
pageSize: 5,
|
||||
searchQuery: search,
|
||||
dependencies: [user?.id],
|
||||
onError: (error) => console.error("[ERROR] Fetch voting:", error),
|
||||
});
|
||||
|
||||
// Gunakan helper untuk membuat komponen-komponen pagination
|
||||
const { ListEmptyComponent, ListFooterComponent } =
|
||||
createPaginationComponents({
|
||||
loading: pagination.loading,
|
||||
refreshing: pagination.refreshing,
|
||||
listData: pagination.listData,
|
||||
searchQuery: search,
|
||||
emptyMessage: "Tidak ada data",
|
||||
emptySearchMessage: "Tidak ada hasil pencarian",
|
||||
skeletonCount: 5,
|
||||
skeletonHeight: 200,
|
||||
isInitialLoad: pagination.isInitialLoad,
|
||||
});
|
||||
|
||||
// Render item untuk FlatList
|
||||
const renderItem = useMemo(
|
||||
() =>
|
||||
({ item }: { item: any }) =>
|
||||
(
|
||||
<Voting_BoxPublishSection
|
||||
data={item}
|
||||
key={item.id}
|
||||
href={`/voting/${item.id}`}
|
||||
/>
|
||||
),
|
||||
[],
|
||||
);
|
||||
|
||||
return (
|
||||
<NewWrapper
|
||||
listData={pagination.listData}
|
||||
renderItem={renderItem}
|
||||
ListEmptyComponent={ListEmptyComponent}
|
||||
ListFooterComponent={ListFooterComponent}
|
||||
onEndReached={pagination.loadMore}
|
||||
refreshControl={
|
||||
<RefreshControl
|
||||
refreshing={pagination.refreshing}
|
||||
onRefresh={pagination.onRefresh}
|
||||
/>
|
||||
}
|
||||
hideFooter
|
||||
floatingButton={
|
||||
<FloatingButton onPress={() => router.push("/voting/create")} />
|
||||
}
|
||||
headerComponent={
|
||||
<SearchInput placeholder="Cari voting" onChangeText={setSearch} />
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
70
screens/Voting/ScreenContribution.tsx
Normal file
70
screens/Voting/ScreenContribution.tsx
Normal file
@@ -0,0 +1,70 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import NewWrapper from "@/components/_ShareComponent/NewWrapper";
|
||||
import { createPaginationComponents } from "@/helpers/paginationHelpers";
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
import { usePagination } from "@/hooks/use-pagination";
|
||||
import Voting_BoxPublishSection from "@/screens/Voting/BoxPublishSection";
|
||||
import { apiVotingGetAll } from "@/service/api-client/api-voting";
|
||||
import { useMemo } from "react";
|
||||
import { RefreshControl } from "react-native";
|
||||
|
||||
export default function Voting_ScreenContribution() {
|
||||
const { user } = useAuth();
|
||||
|
||||
const pagination = usePagination({
|
||||
fetchFunction: async (page) => {
|
||||
return await apiVotingGetAll({
|
||||
category: "contribution",
|
||||
authorId: user?.id as string,
|
||||
page: String(page),
|
||||
});
|
||||
},
|
||||
pageSize: 4,
|
||||
dependencies: [user?.id],
|
||||
onError: (error) => console.error("[ERROR] Fetch contribution:", error),
|
||||
});
|
||||
|
||||
// Gunakan helper untuk membuat komponen-komponen pagination
|
||||
const { ListEmptyComponent, ListFooterComponent } =
|
||||
createPaginationComponents({
|
||||
loading: pagination.loading,
|
||||
refreshing: pagination.refreshing,
|
||||
listData: pagination.listData,
|
||||
emptyMessage: "Tidak ada kontribusi",
|
||||
emptySearchMessage: "Tidak ada hasil pencarian",
|
||||
skeletonCount: 5,
|
||||
skeletonHeight: 200,
|
||||
isInitialLoad: pagination.isInitialLoad,
|
||||
});
|
||||
|
||||
// Render item untuk FlatList
|
||||
const renderItem = useMemo(
|
||||
() =>
|
||||
({ item }: { item: any }) =>
|
||||
(
|
||||
<Voting_BoxPublishSection
|
||||
data={item}
|
||||
key={item.id}
|
||||
href={`/voting/${item.id}/contribution`}
|
||||
/>
|
||||
),
|
||||
[],
|
||||
);
|
||||
|
||||
return (
|
||||
<NewWrapper
|
||||
listData={pagination.listData}
|
||||
renderItem={renderItem}
|
||||
ListEmptyComponent={ListEmptyComponent}
|
||||
ListFooterComponent={ListFooterComponent}
|
||||
onEndReached={pagination.loadMore}
|
||||
refreshControl={
|
||||
<RefreshControl
|
||||
refreshing={pagination.refreshing}
|
||||
onRefresh={pagination.onRefresh}
|
||||
/>
|
||||
}
|
||||
hideFooter
|
||||
/>
|
||||
);
|
||||
}
|
||||
114
screens/Voting/ScreenHistory.tsx
Normal file
114
screens/Voting/ScreenHistory.tsx
Normal file
@@ -0,0 +1,114 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import { ButtonCustom, Spacing, TextCustom } from "@/components";
|
||||
import NewWrapper from "@/components/_ShareComponent/NewWrapper";
|
||||
import { AccentColor, MainColor } from "@/constants/color-palet";
|
||||
import { PAGINATION_DEFAULT_TAKE } from "@/constants/constans-value";
|
||||
import { createPaginationComponents } from "@/helpers/paginationHelpers";
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
import { usePagination } from "@/hooks/use-pagination";
|
||||
import Voting_BoxPublishSection from "@/screens/Voting/BoxPublishSection";
|
||||
import { apiVotingGetAll } from "@/service/api-client/api-voting";
|
||||
import { useState } from "react";
|
||||
import { RefreshControl, View } from "react-native";
|
||||
|
||||
export default function Voting_ScreenHistory() {
|
||||
const [activeCategory, setActiveCategory] = useState<string | null>("all");
|
||||
const { user } = useAuth();
|
||||
|
||||
// Setup pagination
|
||||
const pagination = usePagination({
|
||||
fetchFunction: async (page) => {
|
||||
return await apiVotingGetAll({
|
||||
category: activeCategory === "all" ? "all-history" : "my-history",
|
||||
authorId: user?.id as string,
|
||||
page: String(page),
|
||||
});
|
||||
},
|
||||
pageSize: PAGINATION_DEFAULT_TAKE,
|
||||
dependencies: [user?.id, activeCategory],
|
||||
onError: (error) => console.error("[ERROR] Fetch voting history:", error),
|
||||
});
|
||||
|
||||
// Generate komponen
|
||||
const { ListEmptyComponent, ListFooterComponent } =
|
||||
createPaginationComponents({
|
||||
loading: pagination.loading,
|
||||
refreshing: pagination.refreshing,
|
||||
listData: pagination.listData,
|
||||
emptyMessage: "Belum ada riwayat",
|
||||
skeletonCount: PAGINATION_DEFAULT_TAKE,
|
||||
skeletonHeight: 100,
|
||||
});
|
||||
|
||||
// Render item voting
|
||||
const renderVotingItem = ({ item }: { item: any }) => (
|
||||
<Voting_BoxPublishSection
|
||||
key={item && item?.id}
|
||||
data={item}
|
||||
href={`/voting/${item.id}/history`}
|
||||
/>
|
||||
);
|
||||
|
||||
const handlePress = (item: any) => {
|
||||
setActiveCategory(item);
|
||||
// Reset pagination saat kategori berubah
|
||||
pagination.reset();
|
||||
};
|
||||
|
||||
const headerComponent = (
|
||||
<View
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
padding: 5,
|
||||
backgroundColor: MainColor.soft_darkblue,
|
||||
borderRadius: 50,
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
<ButtonCustom
|
||||
backgroundColor={
|
||||
activeCategory === "all" ? MainColor.yellow : AccentColor.blue
|
||||
}
|
||||
textColor={activeCategory === "all" ? MainColor.black : MainColor.white}
|
||||
style={{ width: "49%" }}
|
||||
onPress={() => handlePress("all")}
|
||||
>
|
||||
Semua Riwayat
|
||||
</ButtonCustom>
|
||||
<Spacing width={"2%"} />
|
||||
<ButtonCustom
|
||||
backgroundColor={
|
||||
activeCategory === "main" ? MainColor.yellow : AccentColor.blue
|
||||
}
|
||||
textColor={
|
||||
activeCategory === "main" ? MainColor.black : MainColor.white
|
||||
}
|
||||
style={{ width: "49%" }}
|
||||
onPress={() => handlePress("main")}
|
||||
>
|
||||
Riwayat Saya
|
||||
</ButtonCustom>
|
||||
</View>
|
||||
);
|
||||
|
||||
return (
|
||||
<NewWrapper
|
||||
headerComponent={headerComponent}
|
||||
listData={pagination.listData}
|
||||
renderItem={renderVotingItem}
|
||||
refreshControl={
|
||||
<RefreshControl
|
||||
tintColor={MainColor.yellow}
|
||||
colors={[MainColor.yellow]}
|
||||
refreshing={pagination.refreshing}
|
||||
onRefresh={pagination.onRefresh}
|
||||
/>
|
||||
}
|
||||
onEndReached={pagination.loadMore}
|
||||
ListEmptyComponent={ListEmptyComponent}
|
||||
ListFooterComponent={ListFooterComponent}
|
||||
hideFooter
|
||||
/>
|
||||
);
|
||||
}
|
||||
80
screens/Voting/ScreenListOfContributor.tsx
Normal file
80
screens/Voting/ScreenListOfContributor.tsx
Normal file
@@ -0,0 +1,80 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
AvatarUsernameAndOtherComponent,
|
||||
BadgeCustom,
|
||||
BaseBox,
|
||||
Spacing,
|
||||
TextCustom,
|
||||
} from "@/components";
|
||||
import NewWrapper from "@/components/_ShareComponent/NewWrapper";
|
||||
import { PAGINATION_DEFAULT_TAKE } from "@/constants/constans-value";
|
||||
import { createPaginationComponents } from "@/helpers/paginationHelpers";
|
||||
import { usePagination } from "@/hooks/use-pagination";
|
||||
import { apiVotingContribution } from "@/service/api-client/api-voting";
|
||||
import { useLocalSearchParams } from "expo-router";
|
||||
import _ from "lodash";
|
||||
import { RefreshControl, View } from "react-native";
|
||||
|
||||
export default function Voting_ScreenListOfContributor() {
|
||||
const { id } = useLocalSearchParams();
|
||||
|
||||
// Setup pagination
|
||||
const pagination = usePagination({
|
||||
fetchFunction: async (page) => {
|
||||
return await apiVotingContribution({
|
||||
id: id as string,
|
||||
authorId: "",
|
||||
category: "list",
|
||||
page: String(page),
|
||||
});
|
||||
},
|
||||
pageSize: PAGINATION_DEFAULT_TAKE,
|
||||
dependencies: [id],
|
||||
onError: (error) =>
|
||||
console.error("[ERROR] Fetch voting contributors:", error),
|
||||
});
|
||||
|
||||
// Generate komponen
|
||||
const { ListEmptyComponent, ListFooterComponent } =
|
||||
createPaginationComponents({
|
||||
loading: pagination.loading,
|
||||
refreshing: pagination.refreshing,
|
||||
listData: pagination.listData,
|
||||
emptyMessage: "Tidak ada kontributor",
|
||||
skeletonCount: PAGINATION_DEFAULT_TAKE,
|
||||
skeletonHeight: 100,
|
||||
});
|
||||
|
||||
// Render item contributor
|
||||
const renderContributorItem = ({ item }: { item: any }) => (
|
||||
<BaseBox paddingTop={5} paddingBottom={5} key={item?.id}>
|
||||
<AvatarUsernameAndOtherComponent
|
||||
avatar={item?.Author?.Profile?.imageId || ""}
|
||||
name={item?.Author?.username || "Username"}
|
||||
avatarHref={`/profile/${item?.Author?.Profile?.id}`}
|
||||
rightComponent={
|
||||
<BadgeCustom style={{ alignSelf: "flex-end" }}>
|
||||
{item?.Voting_DaftarNamaVote?.value}
|
||||
</BadgeCustom>
|
||||
}
|
||||
/>
|
||||
</BaseBox>
|
||||
);
|
||||
|
||||
return (
|
||||
<NewWrapper
|
||||
hideFooter
|
||||
listData={pagination.listData}
|
||||
renderItem={renderContributorItem}
|
||||
refreshControl={
|
||||
<RefreshControl
|
||||
refreshing={pagination.refreshing}
|
||||
onRefresh={pagination.onRefresh}
|
||||
/>
|
||||
}
|
||||
onEndReached={pagination.loadMore}
|
||||
ListEmptyComponent={ListEmptyComponent}
|
||||
ListFooterComponent={ListFooterComponent}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -8,17 +8,18 @@ import {
|
||||
} from "@/components";
|
||||
import NewWrapper from "@/components/_ShareComponent/NewWrapper";
|
||||
import { MainColor } from "@/constants/color-palet";
|
||||
import { PAGINATION_DEFAULT_TAKE } from "@/constants/constans-value";
|
||||
import { createPaginationComponents } from "@/helpers/paginationHelpers";
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
import { usePagination } from "@/hooks/use-pagination";
|
||||
import { dummyMasterStatus } from "@/lib/dummy-data/_master/status";
|
||||
import { apiVotingGetByStatus } from "@/service/api-client/api-voting";
|
||||
import { dateTimeView } from "@/utils/dateTimeView";
|
||||
import { useLocalSearchParams } from "expo-router";
|
||||
import { useState } from "react";
|
||||
import { useFocusEffect, useLocalSearchParams } from "expo-router";
|
||||
import { useCallback, useState } from "react";
|
||||
import { RefreshControl, View } from "react-native";
|
||||
|
||||
const PAGE_SIZE = 10;
|
||||
const PAGE_SIZE = 6
|
||||
|
||||
export default function Voting_ScreenStatus() {
|
||||
const { user } = useAuth();
|
||||
@@ -40,7 +41,7 @@ export default function Voting_ScreenStatus() {
|
||||
page: String(page),
|
||||
});
|
||||
},
|
||||
pageSize: PAGE_SIZE,
|
||||
pageSize: PAGINATION_DEFAULT_TAKE,
|
||||
dependencies: [id, activeCategory],
|
||||
onError: (error) => console.error("[ERROR] Fetch voting by status:", error),
|
||||
});
|
||||
@@ -91,6 +92,12 @@ export default function Voting_ScreenStatus() {
|
||||
pagination.reset();
|
||||
};
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
pagination.onRefresh();
|
||||
}, [activeCategory])
|
||||
);
|
||||
|
||||
const scrollComponent = (
|
||||
<ScrollableCustom
|
||||
data={dummyMasterStatus.map((e, i) => ({
|
||||
|
||||
@@ -52,15 +52,18 @@ export async function apiAdminDonationUpdateStatus({
|
||||
export async function apiAdminDonationListOfDonatur({
|
||||
id,
|
||||
status,
|
||||
page = "1"
|
||||
}: {
|
||||
id: string;
|
||||
status: "berhasil" | "gagal" | "proses" | "menunggu" | null;
|
||||
page?: string;
|
||||
}) {
|
||||
const query = status && status !== null ? `?status=${status}` : "";
|
||||
const statusQuery = status && status !== null ? `&status=${status}` : "";
|
||||
const pageQuery = `&page=${page}`;
|
||||
|
||||
try {
|
||||
const response = await apiConfig.get(
|
||||
`/mobile/admin/donation/${id}/donatur${query}`
|
||||
`/mobile/admin/donation/${id}/donatur?${statusQuery}${pageQuery}`
|
||||
);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
@@ -105,19 +108,6 @@ export async function apiAdminDonationInvoiceUpdateById({
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
|
||||
@@ -40,16 +40,19 @@ export async function apiDonationGetOne({
|
||||
export async function apiDonationGetByStatus({
|
||||
authorId,
|
||||
status,
|
||||
page = "1",
|
||||
}: {
|
||||
authorId: string;
|
||||
status: string;
|
||||
page?: string;
|
||||
}) {
|
||||
const authorQuery = `/${authorId}`;
|
||||
const statusQuery = `/${status}`;
|
||||
const pageQuery = `?page=${page}`;
|
||||
|
||||
try {
|
||||
const response = await apiConfig.get(
|
||||
`/mobile/donation${authorQuery}${statusQuery}`
|
||||
`/mobile/donation${authorQuery}${statusQuery}${pageQuery}`
|
||||
);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
@@ -106,14 +109,17 @@ export async function apiDonationUpdateData({
|
||||
export async function apiDonationGetAll({
|
||||
category,
|
||||
authorId,
|
||||
page = "1"
|
||||
}: {
|
||||
category: "beranda" | "my-donation";
|
||||
authorId?: string;
|
||||
page?: string;
|
||||
}) {
|
||||
const authorQuery = authorId ? `&authorId=${authorId}` : "";
|
||||
const pageQuery = `&page=${page}`;
|
||||
try {
|
||||
const response = await apiConfig.get(
|
||||
`/mobile/donation?category=${category}${authorQuery}`
|
||||
`/mobile/donation?category=${category}${authorQuery}${pageQuery}`
|
||||
);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
@@ -216,13 +222,15 @@ export async function apiDonationCreateNews({
|
||||
export async function apiDonationGetNewsById({
|
||||
id,
|
||||
category,
|
||||
page = "1"
|
||||
}: {
|
||||
id: string;
|
||||
category: "get-all" | "get-one";
|
||||
page?: string;
|
||||
}) {
|
||||
try {
|
||||
const response = await apiConfig.get(
|
||||
`/mobile/donation/${id}/news?category=${category}`
|
||||
`/mobile/donation/${id}/news?category=${category}&page=${page}`
|
||||
);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
@@ -258,11 +266,28 @@ export async function apiDonationDeleteNews({ id }: { id: string }) {
|
||||
|
||||
export async function apiDonationDisbursementOfFundsListById({
|
||||
id,
|
||||
page = "1"
|
||||
}: {
|
||||
id: string;
|
||||
page?: string;
|
||||
}) {
|
||||
try {
|
||||
const response = await apiConfig.get(`/mobile/donation/${id}/disbursement`);
|
||||
const response = await apiConfig.get(`/mobile/donation/${id}/disbursement?page=${page}`);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export async function apiDonationListOfDonaturById({
|
||||
id,
|
||||
page = "1"
|
||||
}: {
|
||||
id: string;
|
||||
page?: string;
|
||||
}) {
|
||||
try {
|
||||
const response = await apiConfig.get(`/mobile/donation/${id}/donatur?page=${page}`);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
throw error;
|
||||
|
||||
@@ -14,13 +14,15 @@ export async function apiInvestmentCreate({ data }: { data: any }) {
|
||||
export async function apiInvestmentGetByStatus({
|
||||
authorId,
|
||||
status,
|
||||
page = "1",
|
||||
}: {
|
||||
authorId: string;
|
||||
status: string;
|
||||
page?: string;
|
||||
}) {
|
||||
try {
|
||||
const response = await apiConfig.get(
|
||||
`/mobile/investment/${authorId}/${status}`
|
||||
`/mobile/investment/${authorId}/${status}?page=${page}`
|
||||
);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
@@ -103,13 +105,15 @@ export async function apiInvestmentUpsertDocument({
|
||||
export async function apiInvestmentGetDocument({
|
||||
id,
|
||||
category,
|
||||
page = "1",
|
||||
}: {
|
||||
id: string;
|
||||
category: "one-document" | "all-document";
|
||||
page?: string;
|
||||
}) {
|
||||
try {
|
||||
const response = await apiConfig.get(
|
||||
`/mobile/investment/${id}/document?category=${category}`
|
||||
`/mobile/investment/${id}/document?category=${category}&page=${page}`
|
||||
);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
@@ -131,15 +135,17 @@ export async function apiInvestmentDeleteDocument({ id }: { id: string }) {
|
||||
export async function apiInvestmentGetAll({
|
||||
category,
|
||||
authorId,
|
||||
page = "1",
|
||||
}: {
|
||||
category: "my-holding" | "bursa";
|
||||
authorId?: string;
|
||||
page?: string;
|
||||
}) {
|
||||
try {
|
||||
const response = await apiConfig.get(
|
||||
`/mobile/investment?category=${category}${
|
||||
authorId ? `&authorId=${authorId}` : ""
|
||||
}`
|
||||
}&page=${page}`
|
||||
);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
@@ -168,16 +174,19 @@ export async function apiInvestmentGetInvoice({
|
||||
id,
|
||||
authorId,
|
||||
category,
|
||||
page = "1",
|
||||
}: {
|
||||
id?: string;
|
||||
authorId?: string;
|
||||
category: "my-invest" | "transaction" | "invoice";
|
||||
page?: string;
|
||||
}) {
|
||||
const categoryQuery = `?category=${category}`;
|
||||
const authorIdQuery = authorId ? `&authorId=${authorId}` : "";
|
||||
const pageQuery = `&page=${page}`;
|
||||
try {
|
||||
const response = await apiConfig.get(
|
||||
`/mobile/investment/${id}/invoice${categoryQuery}${authorIdQuery}`
|
||||
`/mobile/investment/${id}/invoice${categoryQuery}${authorIdQuery}${pageQuery}`
|
||||
);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
@@ -229,13 +238,15 @@ export async function apiInvestmentCreateNews({
|
||||
export async function apiInvestmentGetNews({
|
||||
id,
|
||||
category,
|
||||
page = "1",
|
||||
}: {
|
||||
id: string;
|
||||
category: "all-news" | "one-news";
|
||||
page?: string;
|
||||
}) {
|
||||
try {
|
||||
const response = await apiConfig.get(
|
||||
`/mobile/investment/${id}/news?category=${category}`
|
||||
`/mobile/investment/${id}/news?category=${category}&page=${page}`
|
||||
);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
@@ -254,11 +265,13 @@ export async function apiInvestmentDeleteNews({ id }: { id: string }) {
|
||||
|
||||
export async function apiInvestmentGetInvestorById({
|
||||
id,
|
||||
page = "1",
|
||||
}: {
|
||||
id: string;
|
||||
page?: string;
|
||||
}) {
|
||||
try {
|
||||
const response = await apiConfig.get(`/mobile/investment/${id}/investor`);
|
||||
const response = await apiConfig.get(`/mobile/investment/${id}/investor?page=${page}`);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
throw error;
|
||||
|
||||
@@ -81,14 +81,15 @@ export async function apiVotingUpdateData({
|
||||
}
|
||||
}
|
||||
|
||||
export async function apiVotingGetAll({ search, category, authorId, userLoginId }: { search?: string, category: "beranda" | "contribution" | "all-history" | "my-history", authorId?: string, userLoginId?: string }) {
|
||||
export async function apiVotingGetAll({ search, category, authorId, userLoginId, page = "1" }: { search?: string, category: "beranda" | "contribution" | "all-history" | "my-history", authorId?: string, userLoginId?: string, page?: string }) {
|
||||
try {
|
||||
console.log("userLoginId", userLoginId);
|
||||
const categoryQuery = category ? `?category=${category}` : "";
|
||||
const searchQuery = search ? `&search=${search}` : "";
|
||||
const authorIdQuery = authorId ? `&authorId=${authorId}` : "";
|
||||
const userLoginIdQuery = userLoginId ? `&userLoginId=${userLoginId}` : "";
|
||||
const response = await apiConfig.get(`/mobile/voting${categoryQuery}${searchQuery}${authorIdQuery}${userLoginIdQuery}`);
|
||||
const pageQuery = `&page=${page}`;
|
||||
const response = await apiConfig.get(`/mobile/voting${categoryQuery}${searchQuery}${authorIdQuery}${userLoginIdQuery}${pageQuery}`);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
throw error;
|
||||
@@ -110,15 +111,17 @@ export async function apiVotingContribution({
|
||||
id,
|
||||
authorId,
|
||||
category,
|
||||
page = "1",
|
||||
}: {
|
||||
id: string;
|
||||
authorId: string;
|
||||
category: "list" | "checked";
|
||||
page?: string;
|
||||
}) {
|
||||
const query =
|
||||
category === "list"
|
||||
? "?category=list"
|
||||
: `?category=checked&authorId=${authorId}`;
|
||||
? `?category=list&page=${page}`
|
||||
: `?category=checked&authorId=${authorId}&page=${page}`;
|
||||
try {
|
||||
const response = await apiConfig.get(
|
||||
`/mobile/voting/${id}/contribution${query}`
|
||||
|
||||
@@ -47,9 +47,8 @@ export async function apiGetNotificationsById({
|
||||
category: TypeNotificationCategoryApp;
|
||||
page?: string;
|
||||
}) {
|
||||
console.log("ID", id);
|
||||
|
||||
console.log("Category", category);
|
||||
console.log("Page", page);
|
||||
|
||||
try {
|
||||
const response = await apiConfig.get(
|
||||
|
||||
@@ -31,5 +31,5 @@ export const formatChatTime = (date: string | Date): string => {
|
||||
}
|
||||
|
||||
// Lebih dari 7 hari lalu
|
||||
return messageDate.format('DD - MM - YYYY, HH.mm'); // "05 - 11 - 2025, 14.00"
|
||||
return messageDate.format('DD/MM/YYYY, HH.mm'); // "05/11/2025, 14.00"
|
||||
};
|
||||
Reference in New Issue
Block a user