Compare commits
2 Commits
loaddata/6
...
loaddata/9
| Author | SHA1 | Date | |
|---|---|---|---|
| 2705f96b01 | |||
| 38a6b424e8 |
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,82 +1,10 @@
|
|||||||
/* eslint-disable react-hooks/exhaustive-deps */
|
import Donation_ScreenStatus from "@/screens/Donation/ScreenStatus";
|
||||||
import {
|
import { useLocalSearchParams } from "expo-router";
|
||||||
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";
|
|
||||||
|
|
||||||
export default function DonationStatus() {
|
export default function DonationStatus() {
|
||||||
const { user } = useAuth();
|
|
||||||
const { status } = useLocalSearchParams<{ status?: string }>();
|
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 (
|
return (
|
||||||
<ViewWrapper hideFooter headerComponent={scrollComponent}>
|
<Donation_ScreenStatus initialStatus={status || "publish"} />
|
||||||
{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>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ export default function DonasiDetailStatus() {
|
|||||||
useFocusEffect(
|
useFocusEffect(
|
||||||
useCallback(() => {
|
useCallback(() => {
|
||||||
onLoadData();
|
onLoadData();
|
||||||
}, [id])
|
}, [id]),
|
||||||
);
|
);
|
||||||
|
|
||||||
const onLoadData = async () => {
|
const onLoadData = async () => {
|
||||||
@@ -99,6 +99,7 @@ export default function DonasiDetailStatus() {
|
|||||||
sisaHari={value.sisa}
|
sisaHari={value.sisa}
|
||||||
reminder={value.reminder}
|
reminder={value.reminder}
|
||||||
data={data}
|
data={data}
|
||||||
|
showSisaHari={status === "publish" ? true : false}
|
||||||
bottomSection={
|
bottomSection={
|
||||||
status === "publish" && (
|
status === "publish" && (
|
||||||
<Donation_ProgressSection
|
<Donation_ProgressSection
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
/* eslint-disable react-hooks/exhaustive-deps */
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
import {
|
import {
|
||||||
|
BoxButtonOnFooter,
|
||||||
ButtonCenteredOnly,
|
ButtonCenteredOnly,
|
||||||
ButtonCustom,
|
ButtonCustom,
|
||||||
InformationBox,
|
InformationBox,
|
||||||
LandscapeFrameUploaded,
|
LandscapeFrameUploaded,
|
||||||
LoaderCustom,
|
LoaderCustom,
|
||||||
|
NewWrapper,
|
||||||
SelectCustom,
|
SelectCustom,
|
||||||
Spacing,
|
Spacing,
|
||||||
StackCustom,
|
StackCustom,
|
||||||
@@ -60,7 +62,7 @@ export default function DonationEdit() {
|
|||||||
useCallback(() => {
|
useCallback(() => {
|
||||||
onLoadData();
|
onLoadData();
|
||||||
onLoadList();
|
onLoadList();
|
||||||
}, [id])
|
}, [id]),
|
||||||
);
|
);
|
||||||
|
|
||||||
const onLoadData = async () => {
|
const onLoadData = async () => {
|
||||||
@@ -79,7 +81,6 @@ export default function DonationEdit() {
|
|||||||
imageId: response.data.imageId,
|
imageId: response.data.imageId,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log("[ERROR]", error);
|
console.log("[ERROR]", error);
|
||||||
}
|
}
|
||||||
@@ -182,7 +183,21 @@ export default function DonationEdit() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
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." />
|
<InformationBox text="Lengkapi semua data di bawah untuk selanjutnya mengisi cerita penggalangan dana." />
|
||||||
{!data || loadList ? (
|
{!data || loadList ? (
|
||||||
<LoaderCustom />
|
<LoaderCustom />
|
||||||
@@ -260,17 +275,9 @@ export default function DonationEdit() {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<Spacing />
|
<Spacing />
|
||||||
<ButtonCustom
|
|
||||||
isLoading={isLoading}
|
|
||||||
onPress={() => {
|
|
||||||
handlerSubmitUpdate();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Update
|
|
||||||
</ButtonCustom>
|
|
||||||
</StackCustom>
|
</StackCustom>
|
||||||
)}
|
)}
|
||||||
<Spacing />
|
<Spacing />
|
||||||
</ViewWrapper>
|
</NewWrapper>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
/* eslint-disable react-hooks/exhaustive-deps */
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
import {
|
import {
|
||||||
|
BoxButtonOnFooter,
|
||||||
ButtonCenteredOnly,
|
ButtonCenteredOnly,
|
||||||
ButtonCustom,
|
ButtonCustom,
|
||||||
InformationBox,
|
InformationBox,
|
||||||
@@ -8,8 +9,8 @@ import {
|
|||||||
StackCustom,
|
StackCustom,
|
||||||
TextAreaCustom,
|
TextAreaCustom,
|
||||||
TextInputCustom,
|
TextInputCustom,
|
||||||
ViewWrapper,
|
|
||||||
} from "@/components";
|
} from "@/components";
|
||||||
|
import NewWrapper from "@/components/_ShareComponent/NewWrapper";
|
||||||
import DIRECTORY_ID from "@/constants/directory-id";
|
import DIRECTORY_ID from "@/constants/directory-id";
|
||||||
import { useAuth } from "@/hooks/use-auth";
|
import { useAuth } from "@/hooks/use-auth";
|
||||||
import {
|
import {
|
||||||
@@ -112,7 +113,23 @@ export default function DonationCreateStory() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ViewWrapper>
|
<NewWrapper
|
||||||
|
hideFooter
|
||||||
|
footerComponent={
|
||||||
|
<>
|
||||||
|
<BoxButtonOnFooter>
|
||||||
|
<ButtonCustom
|
||||||
|
isLoading={isLoading}
|
||||||
|
onPress={() => {
|
||||||
|
handlerSubmit();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Simpan
|
||||||
|
</ButtonCustom>
|
||||||
|
</BoxButtonOnFooter>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
>
|
||||||
<StackCustom gap={"xs"}>
|
<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." />
|
<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
|
<TextAreaCustom
|
||||||
@@ -166,18 +183,8 @@ export default function DonationCreateStory() {
|
|||||||
value={data.rekening}
|
value={data.rekening}
|
||||||
onChangeText={(value) => setData({ ...data, rekening: value })}
|
onChangeText={(value) => setData({ ...data, rekening: value })}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Spacing />
|
|
||||||
<ButtonCustom
|
|
||||||
isLoading={isLoading}
|
|
||||||
onPress={() => {
|
|
||||||
handlerSubmit();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Simpan
|
|
||||||
</ButtonCustom>
|
|
||||||
</StackCustom>
|
</StackCustom>
|
||||||
<Spacing />
|
<Spacing />
|
||||||
</ViewWrapper>
|
</NewWrapper>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import {
|
import {
|
||||||
|
BoxButtonOnFooter,
|
||||||
ButtonCenteredOnly,
|
ButtonCenteredOnly,
|
||||||
ButtonCustom,
|
ButtonCustom,
|
||||||
InformationBox,
|
InformationBox,
|
||||||
@@ -8,8 +9,8 @@ import {
|
|||||||
Spacing,
|
Spacing,
|
||||||
StackCustom,
|
StackCustom,
|
||||||
TextInputCustom,
|
TextInputCustom,
|
||||||
ViewWrapper,
|
|
||||||
} from "@/components";
|
} from "@/components";
|
||||||
|
import NewWrapper from "@/components/_ShareComponent/NewWrapper";
|
||||||
import DIRECTORY_ID from "@/constants/directory-id";
|
import DIRECTORY_ID from "@/constants/directory-id";
|
||||||
import { apiDonationCreate } from "@/service/api-client/api-donation";
|
import { apiDonationCreate } from "@/service/api-client/api-donation";
|
||||||
import { apiMasterDonation } from "@/service/api-client/api-master";
|
import { apiMasterDonation } from "@/service/api-client/api-master";
|
||||||
@@ -43,7 +44,7 @@ export default function DonationCreate() {
|
|||||||
useFocusEffect(
|
useFocusEffect(
|
||||||
useCallback(() => {
|
useCallback(() => {
|
||||||
onLoadList();
|
onLoadList();
|
||||||
}, [])
|
}, []),
|
||||||
);
|
);
|
||||||
|
|
||||||
const onLoadList = async () => {
|
const onLoadList = async () => {
|
||||||
@@ -125,7 +126,24 @@ export default function DonationCreate() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ViewWrapper>
|
<NewWrapper
|
||||||
|
hideFooter
|
||||||
|
footerComponent={
|
||||||
|
<>
|
||||||
|
<BoxButtonOnFooter>
|
||||||
|
<ButtonCustom
|
||||||
|
isLoading={isLoading}
|
||||||
|
onPress={() => {
|
||||||
|
handlerSubmit();
|
||||||
|
// router.push(`/donation/create-story?id=${"dasdsadsa"}`);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Selanjutnya
|
||||||
|
</ButtonCustom>
|
||||||
|
</BoxButtonOnFooter>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
>
|
||||||
<StackCustom gap={"xs"}>
|
<StackCustom gap={"xs"}>
|
||||||
<InformationBox text="Lengkapi semua data di bawah untuk selanjutnya mengisi cerita penggalangan dana." />
|
<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 })}
|
onChange={(value: any) => setData({ ...data, durasiId: value })}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Spacing />
|
|
||||||
<ButtonCustom
|
|
||||||
isLoading={isLoading}
|
|
||||||
onPress={() => {
|
|
||||||
handlerSubmit();
|
|
||||||
// router.push(`/donation/create-story?id=${"dasdsadsa"}`);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Selanjutnya
|
|
||||||
</ButtonCustom>
|
|
||||||
<Spacing />
|
|
||||||
</StackCustom>
|
</StackCustom>
|
||||||
<Spacing />
|
<Spacing />
|
||||||
</ViewWrapper>
|
</NewWrapper>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,83 +1,10 @@
|
|||||||
/* eslint-disable react-hooks/exhaustive-deps */
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
import {
|
import Investment_ScreenMyHolding from "@/screens/Invesment/ScreenMyHolding";
|
||||||
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";
|
|
||||||
|
|
||||||
export default function InvestmentMyHolding() {
|
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 (
|
return (
|
||||||
<ViewWrapper hideFooter>
|
<>
|
||||||
{loadingList ? (
|
<Investment_ScreenMyHolding />
|
||||||
<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>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
/* eslint-disable react-hooks/exhaustive-deps */
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
import Investment_ScreenRecap from "@/screens/Invesment/Document/ScreenRecap";
|
import Investment_ScreenRecapOfDocument from "@/screens/Invesment/Document/ScreenRecapOfDocument";
|
||||||
|
|
||||||
export default function InvestmentRecapOfDocument() {
|
export default function InvestmentRecapOfDocument() {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Investment_ScreenRecap />
|
<Investment_ScreenRecapOfDocument />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,100 +1,10 @@
|
|||||||
/* eslint-disable react-hooks/exhaustive-deps */
|
import Investment_ScreenListOfNews from "@/screens/Invesment/News/ScreenListOfNews";
|
||||||
import {
|
import { useLocalSearchParams } from "expo-router";
|
||||||
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";
|
|
||||||
|
|
||||||
export default function InvestmentListOfNews() {
|
export default function InvestmentListOfNews() {
|
||||||
const { id } = useLocalSearchParams();
|
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 (
|
return (
|
||||||
<>
|
<Investment_ScreenListOfNews investmentId={id as string} />
|
||||||
<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>
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,101 +1,10 @@
|
|||||||
/* eslint-disable react-hooks/exhaustive-deps */
|
import Investment_ScreenRecapOfNews from "@/screens/Invesment/News/ScreenRecapOfNews";
|
||||||
import {
|
import { useLocalSearchParams } from "expo-router";
|
||||||
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";
|
|
||||||
|
|
||||||
export default function InvestmentRecapOfNews() {
|
export default function InvestmentRecapOfNews() {
|
||||||
const { id } = useLocalSearchParams();
|
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 (
|
return (
|
||||||
<>
|
<Investment_ScreenRecapOfNews investmentId={id as string} />
|
||||||
<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>
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
/* eslint-disable react-hooks/exhaustive-deps */
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
import Investment_ScreenTransaction from "@/screens/Invesment/ScreenTransaction";
|
import Investment_ScreenInvoice from "@/screens/Invesment/ScreenInvoice";
|
||||||
|
|
||||||
export default function InvestmentInvoice() {
|
export default function InvestmentInvoice() {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Investment_ScreenTransaction />
|
<Investment_ScreenInvoice />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -73,7 +73,6 @@ export default function InvestmentDetailStatus() {
|
|||||||
updateCountDown();
|
updateCountDown();
|
||||||
}, [data]);
|
}, [data]);
|
||||||
|
|
||||||
console.log("[DATA DETAIL]", JSON.stringify(data, null, 2));
|
|
||||||
|
|
||||||
const updateCountDown = () => {
|
const updateCountDown = () => {
|
||||||
const countDown = countDownAndCondition({
|
const countDown = countDownAndCondition({
|
||||||
|
|||||||
@@ -72,7 +72,6 @@ export default function InvestmentDetail() {
|
|||||||
updateCountDown();
|
updateCountDown();
|
||||||
}, [data]);
|
}, [data]);
|
||||||
|
|
||||||
console.log("[DATA DETAIL]", JSON.stringify(data, null, 2));
|
|
||||||
|
|
||||||
const updateCountDown = () => {
|
const updateCountDown = () => {
|
||||||
const countDown = countDownAndCondition({
|
const countDown = countDownAndCondition({
|
||||||
|
|||||||
@@ -1,67 +1,10 @@
|
|||||||
/* eslint-disable react-hooks/exhaustive-deps */
|
import Investment_ScreenInvestor from "@/screens/Invesment/ScreenInvestor";
|
||||||
import {
|
import { useLocalSearchParams } from "expo-router";
|
||||||
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";
|
|
||||||
|
|
||||||
export default function InvestmentInvestor() {
|
export default function InvestmentInvestor() {
|
||||||
const { id } = useLocalSearchParams();
|
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 (
|
return (
|
||||||
<>
|
<Investment_ScreenInvestor investmentId={id as string} />
|
||||||
<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>
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,33 @@
|
|||||||
<!-- ===================== Start Penerapan Pagination ===================== -->
|
<!-- ===================== Start Penerapan Pagination ===================== -->
|
||||||
|
|
||||||
|
File source: app/(application)/(user)/donation/(tabs)/status.tsx
|
||||||
|
Folder tujuan: screens/Donation
|
||||||
|
Nama file utama: ScreenStatus.tsx
|
||||||
|
|
||||||
|
Buat file baru pada "Folder tujuan" dengan nama "Nama file utama" dan ubah nama function menjadi "Donation_ScreenStatus" kemudian clean code, import dan panggil function tersebut pada file "File source"
|
||||||
|
Selanjutnya terapkan pagination pada file "Nama file utama"
|
||||||
|
|
||||||
|
Function fecth: apiDonationGetByStatus
|
||||||
|
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/Event/ScreenStatus.tsx
|
||||||
|
Anda bisa menggunakan refrensi dari "File refrensi" jika butuh pemahaman dengan tipe fitur yang sama
|
||||||
|
|
||||||
|
<!-- ===================== End Penerapan Pagination ===================== -->
|
||||||
|
|
||||||
|
<!-- ===================== Start Penerapan NewWrapper ===================== -->
|
||||||
File utama: screens/Invesment/ScreenTransaction.tsx
|
File utama: screens/Invesment/ScreenTransaction.tsx
|
||||||
Function fecth: apiInvestmentGetInvoice
|
Function fecth: apiInvestmentGetInvoice
|
||||||
File function fetch: service/api-client/api-investment.ts
|
File function fetch: service/api-client/api-investment.ts
|
||||||
@@ -15,16 +43,12 @@ Jika tidak ada props page maka tambahkan props page dan default page: "1"
|
|||||||
|
|
||||||
Gunakan bahasa indonesia pada cli agar saya mudah membacanya.
|
Gunakan bahasa indonesia pada cli agar saya mudah membacanya.
|
||||||
|
|
||||||
<!-- Additional Prompt -->
|
<!-- ===================== End Penerapan NewWrapper ===================== -->
|
||||||
File refrensi: screens/Event/ScreenStatus.tsx
|
|
||||||
Anda bisa menggunakan refrensi dari "File refrensi" jika butuh pemahaman dengan tipe fitur yang sama
|
|
||||||
|
|
||||||
<!-- ===================== End Penerapan Pagination ===================== -->
|
|
||||||
|
|
||||||
<!-- Start Penerapan NewWrapper -->
|
<!-- Start Penerapan NewWrapper -->
|
||||||
Terapkan NewWrapper pada file: screens/Forum/DetailForum.tsx
|
Terapkan NewWrapper pada file: app/(application)/(user)/donation/create.tsx
|
||||||
Component yang digunakan: components/_ShareComponent/NewWrapper.tsx , karena ini adalah halaman detail saya ingin anda fokus pada props pada NewWrapper. Seperti
|
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
|
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"
|
Saya ingin jika didalam deeplink ada "/admin/..." contoh "/admin/event/review/status" maka path yang akan di redirect adalah "/admin/event/review/status"
|
||||||
|
|||||||
@@ -16,6 +16,9 @@ export default function Donation_ButtonStatusSection({
|
|||||||
}) {
|
}) {
|
||||||
const [isLoading, setLoading] = useState(false);
|
const [isLoading, setLoading] = useState(false);
|
||||||
const [isLoadingDelete, setLoadingDelete] = useState(false);
|
const [isLoadingDelete, setLoadingDelete] = useState(false);
|
||||||
|
const path: any = (status: string) => {
|
||||||
|
return `/donation/(tabs)/status?status=${status}`;
|
||||||
|
};
|
||||||
const handleBatalkanReview = async () => {
|
const handleBatalkanReview = async () => {
|
||||||
AlertDefaultSystem({
|
AlertDefaultSystem({
|
||||||
title: "Batalkan Review",
|
title: "Batalkan Review",
|
||||||
@@ -43,7 +46,7 @@ export default function Donation_ButtonStatusSection({
|
|||||||
text1: response.message,
|
text1: response.message,
|
||||||
});
|
});
|
||||||
|
|
||||||
router.back();
|
router.push(path("draft"));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log("[ERROR]", error);
|
console.log("[ERROR]", error);
|
||||||
} finally {
|
} finally {
|
||||||
@@ -80,7 +83,7 @@ export default function Donation_ButtonStatusSection({
|
|||||||
text1: response.message,
|
text1: response.message,
|
||||||
});
|
});
|
||||||
|
|
||||||
router.back();
|
router.replace(path("review"));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log("[ERROR]", error);
|
console.log("[ERROR]", error);
|
||||||
} finally {
|
} finally {
|
||||||
@@ -117,7 +120,7 @@ export default function Donation_ButtonStatusSection({
|
|||||||
text1: response.message,
|
text1: response.message,
|
||||||
});
|
});
|
||||||
|
|
||||||
router.back();
|
router.replace(path("draft"));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log("[ERROR]", error);
|
console.log("[ERROR]", error);
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@@ -12,11 +12,13 @@ import { View } from "react-native";
|
|||||||
export default function Donation_ComponentBoxDetailData({
|
export default function Donation_ComponentBoxDetailData({
|
||||||
bottomSection,
|
bottomSection,
|
||||||
data,
|
data,
|
||||||
|
showSisaHari = true,
|
||||||
sisaHari,
|
sisaHari,
|
||||||
reminder,
|
reminder,
|
||||||
}: {
|
}: {
|
||||||
bottomSection?: React.ReactNode;
|
bottomSection?: React.ReactNode;
|
||||||
data: any;
|
data: any;
|
||||||
|
showSisaHari?: boolean;
|
||||||
sisaHari: number;
|
sisaHari: number;
|
||||||
reminder: boolean;
|
reminder: boolean;
|
||||||
}) {
|
}) {
|
||||||
@@ -34,9 +36,9 @@ export default function Donation_ComponentBoxDetailData({
|
|||||||
<TextCustom bold color="red">
|
<TextCustom bold color="red">
|
||||||
Waktu berakhir
|
Waktu berakhir
|
||||||
</TextCustom>
|
</TextCustom>
|
||||||
) : (
|
) : showSisaHari ? (
|
||||||
<TextCustom>Sisa hari: {sisaHari}</TextCustom>
|
<TextCustom>Sisa hari: {sisaHari}</TextCustom>
|
||||||
)}
|
) : null}
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
<Grid>
|
<Grid>
|
||||||
|
|||||||
95
screens/Donation/ScreenStatus.tsx
Normal file
95
screens/Donation/ScreenStatus.tsx
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
|
import {
|
||||||
|
LoaderCustom,
|
||||||
|
ScrollableCustom,
|
||||||
|
TextCustom,
|
||||||
|
} from "@/components";
|
||||||
|
import NewWrapper from "@/components/_ShareComponent/NewWrapper";
|
||||||
|
import { useAuth } from "@/hooks/use-auth";
|
||||||
|
import { dummyMasterStatus } from "@/lib/dummy-data/_master/status";
|
||||||
|
import Donasi_BoxStatus from "@/screens/Donation/BoxStatus";
|
||||||
|
import { usePagination } from "@/hooks/use-pagination";
|
||||||
|
import { apiDonationGetByStatus } from "@/service/api-client/api-donation";
|
||||||
|
import { useFocusEffect } from "expo-router";
|
||||||
|
import _ from "lodash";
|
||||||
|
import { useCallback, useState } from "react";
|
||||||
|
import { RefreshControl } from "react-native";
|
||||||
|
import { createPaginationComponents } from "@/helpers/paginationHelpers";
|
||||||
|
|
||||||
|
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: 5, // 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);
|
||||||
|
};
|
||||||
|
|
||||||
|
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: 200,
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<NewWrapper
|
||||||
|
listData={pagination.listData}
|
||||||
|
renderItem={renderItem}
|
||||||
|
onEndReached={pagination.loadMore}
|
||||||
|
ListEmptyComponent={ListEmptyComponent}
|
||||||
|
ListFooterComponent={ListFooterComponent}
|
||||||
|
refreshControl={
|
||||||
|
<RefreshControl
|
||||||
|
refreshing={pagination.refreshing}
|
||||||
|
onRefresh={pagination.onRefresh}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
hideFooter
|
||||||
|
headerComponent={scrollComponent}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -4,14 +4,12 @@ import {
|
|||||||
BackButton,
|
BackButton,
|
||||||
DotButton,
|
DotButton,
|
||||||
DrawerCustom,
|
DrawerCustom,
|
||||||
MenuDrawerDynamicGrid,
|
MenuDrawerDynamicGrid
|
||||||
TextCustom,
|
|
||||||
} from "@/components";
|
} from "@/components";
|
||||||
import NewWrapper from "@/components/_ShareComponent/NewWrapper";
|
|
||||||
import { IconEdit } from "@/components/_Icon";
|
import { IconEdit } from "@/components/_Icon";
|
||||||
import { PAGINATION_DEFAULT_TAKE } from "@/constants/constans-value";
|
import NewWrapper from "@/components/_ShareComponent/NewWrapper";
|
||||||
import { MainColor } from "@/constants/color-palet";
|
import { 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 { createPaginationComponents } from "@/helpers/paginationHelpers";
|
||||||
import { usePagination } from "@/hooks/use-pagination";
|
import { usePagination } from "@/hooks/use-pagination";
|
||||||
import Investment_BoxDetailDocument from "@/screens/Invesment/Document/RecapBoxDetail";
|
import Investment_BoxDetailDocument from "@/screens/Invesment/Document/RecapBoxDetail";
|
||||||
@@ -26,12 +24,11 @@ import {
|
|||||||
useFocusEffect,
|
useFocusEffect,
|
||||||
useLocalSearchParams,
|
useLocalSearchParams,
|
||||||
} from "expo-router";
|
} from "expo-router";
|
||||||
import _ from "lodash";
|
|
||||||
import { useCallback, useState } from "react";
|
import { useCallback, useState } from "react";
|
||||||
import { RefreshControl } from "react-native";
|
import { RefreshControl } from "react-native";
|
||||||
import Toast from "react-native-toast-message";
|
import Toast from "react-native-toast-message";
|
||||||
|
|
||||||
export default function Investment_ScreenRecap() {
|
export default function Investment_ScreenRecapOfDocument() {
|
||||||
const { id } = useLocalSearchParams();
|
const { id } = useLocalSearchParams();
|
||||||
const [openDrawer, setOpenDrawer] = useState(false);
|
const [openDrawer, setOpenDrawer] = useState(false);
|
||||||
const [openDrawerBox, setOpenDrawerBox] = useState(false);
|
const [openDrawerBox, setOpenDrawerBox] = useState(false);
|
||||||
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>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
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
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -65,6 +65,8 @@ export default function Investment_ScreenTransaction() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handlePress = ({ id, status }: { id: string; status: string }) => {
|
const handlePress = ({ id, status }: { id: string; status: string }) => {
|
||||||
|
console.log("ID", id);
|
||||||
|
console.log("Status", status);
|
||||||
if (status === "menunggu") {
|
if (status === "menunggu") {
|
||||||
router.push(`/investment/${id}/(transaction-flow)/invoice`);
|
router.push(`/investment/${id}/(transaction-flow)/invoice`);
|
||||||
} else if (status === "proses") {
|
} else if (status === "proses") {
|
||||||
|
|||||||
@@ -40,16 +40,19 @@ export async function apiDonationGetOne({
|
|||||||
export async function apiDonationGetByStatus({
|
export async function apiDonationGetByStatus({
|
||||||
authorId,
|
authorId,
|
||||||
status,
|
status,
|
||||||
|
page = "1",
|
||||||
}: {
|
}: {
|
||||||
authorId: string;
|
authorId: string;
|
||||||
status: string;
|
status: string;
|
||||||
|
page?: string;
|
||||||
}) {
|
}) {
|
||||||
const authorQuery = `/${authorId}`;
|
const authorQuery = `/${authorId}`;
|
||||||
const statusQuery = `/${status}`;
|
const statusQuery = `/${status}`;
|
||||||
|
const pageQuery = `?page=${page}`;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await apiConfig.get(
|
const response = await apiConfig.get(
|
||||||
`/mobile/donation${authorQuery}${statusQuery}`
|
`/mobile/donation${authorQuery}${statusQuery}${pageQuery}`
|
||||||
);
|
);
|
||||||
return response.data;
|
return response.data;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -238,13 +238,15 @@ export async function apiInvestmentCreateNews({
|
|||||||
export async function apiInvestmentGetNews({
|
export async function apiInvestmentGetNews({
|
||||||
id,
|
id,
|
||||||
category,
|
category,
|
||||||
|
page = "1",
|
||||||
}: {
|
}: {
|
||||||
id: string;
|
id: string;
|
||||||
category: "all-news" | "one-news";
|
category: "all-news" | "one-news";
|
||||||
|
page?: string;
|
||||||
}) {
|
}) {
|
||||||
try {
|
try {
|
||||||
const response = await apiConfig.get(
|
const response = await apiConfig.get(
|
||||||
`/mobile/investment/${id}/news?category=${category}`
|
`/mobile/investment/${id}/news?category=${category}&page=${page}`
|
||||||
);
|
);
|
||||||
return response.data;
|
return response.data;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -263,11 +265,13 @@ export async function apiInvestmentDeleteNews({ id }: { id: string }) {
|
|||||||
|
|
||||||
export async function apiInvestmentGetInvestorById({
|
export async function apiInvestmentGetInvestorById({
|
||||||
id,
|
id,
|
||||||
|
page = "1",
|
||||||
}: {
|
}: {
|
||||||
id: string;
|
id: string;
|
||||||
|
page?: string;
|
||||||
}) {
|
}) {
|
||||||
try {
|
try {
|
||||||
const response = await apiConfig.get(`/mobile/investment/${id}/investor`);
|
const response = await apiConfig.get(`/mobile/investment/${id}/investor?page=${page}`);
|
||||||
return response.data;
|
return response.data;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw error;
|
throw error;
|
||||||
|
|||||||
Reference in New Issue
Block a user