Compare commits
168 Commits
auth/21-au
...
97e1f50660
| Author | SHA1 | Date | |
|---|---|---|---|
| 97e1f50660 | |||
| 1cbe4ab330 | |||
| 42fa80c228 | |||
| fb697366fe | |||
| 6d71c3a86f | |||
| e030b8f486 | |||
| 5c931b069c | |||
| b2be7be533 | |||
| 2705f96b01 | |||
| 38a6b424e8 | |||
| 83fa277e03 | |||
| c570a19d84 | |||
| 7415c8c8ce | |||
| 72a3d42013 | |||
| d0abd14047 | |||
| 5b2be20469 | |||
| 60177a1087 | |||
| 771ae45f26 | |||
| 41f4a8ac99 | |||
| 48196cd46b | |||
| ec79a1fbcd | |||
| ed16f1b204 | |||
| d693550a1f | |||
| b3bfbc0f7e | |||
| 71e45d06cc | |||
| 07e64c335e | |||
| 1aebc9b4e8 | |||
| 5665dc88ba | |||
| da82a02a45 | |||
| 14c0f0e499 | |||
| 0262423c50 | |||
| c2682246d6 | |||
| 465e01015e | |||
| 3b15871ad4 | |||
| 9123e73606 | |||
| 6e2046467f | |||
| ca33dd83bb | |||
| ea3fbdc541 | |||
| 33cd47aaed | |||
| 57ac1eb45e | |||
| 145ad73616 | |||
| 7c85e35c61 | |||
| d098b8ca16 | |||
| 73a473cdc7 | |||
| 3f85f330d2 | |||
| 7743a2467c | |||
| 54611ef812 | |||
| 1503707eed | |||
| a01a9bd93f | |||
| 05c1cac10f | |||
| d27c01ed56 | |||
| 34680a4c38 | |||
| 43c8c105cf | |||
| 2c0198b1b7 | |||
| 573b525352 | |||
| 6f9481c7c9 | |||
| cccb44a835 | |||
| 0f5862ce70 | |||
| 624bd49f69 | |||
| 2446e9d51a | |||
| ab5733f336 | |||
| f5e30087ed | |||
| a93f97ed6a | |||
| 858b441a8c | |||
| 98aaa126a1 | |||
| 69452ff4e7 | |||
| 33ec892ec8 | |||
| 8a900e9469 | |||
| d471682ae7 | |||
| 00eea71248 | |||
| 41e648d8f3 | |||
| 0c4deac6e2 | |||
| 676b8a38be | |||
| 0a2aa71013 | |||
| 868e96a54a | |||
| 059b4d053a | |||
| 76debfd6a6 | |||
| 8c3aec8e57 | |||
| 1ade69ff2f | |||
| 97ea6ab799 | |||
| 4e9ce07759 | |||
| 8f659c2b7e | |||
| 61bca7cfe1 | |||
| 5af85c3a8b | |||
| a8807d88ad | |||
| 5d36429aa4 | |||
| ec49999f99 | |||
| 867e82c6fa | |||
| f9f996f195 | |||
| 98394309e6 | |||
| 4625831377 | |||
| ebd6107c36 | |||
| f23cfe1107 | |||
| f9d9b5fbaa | |||
| b3209dc7ee | |||
| 31c1b35173 | |||
| 1e1b18f860 | |||
| de0280367f | |||
| 5d4328a139 | |||
| c8b14b816f | |||
| 125bf16605 | |||
| 73a803f2e8 | |||
| 1bcd1a044f | |||
| 1e0b72de22 | |||
| 36dbfa3296 | |||
| 966e55597c | |||
| 4da55a5a8a | |||
| faf0f36e53 | |||
| 57285e5697 | |||
| 1fd9694ebf | |||
| d31df8c390 | |||
| 90cfb042d8 | |||
| b9f93ff46a | |||
| 0770237fe5 | |||
| 6f4dd79568 | |||
| 9faa0b0f64 | |||
| e05a7c8701 | |||
| f50c5099d8 | |||
| 5f36620988 | |||
| f750d158be | |||
| 0e7b29bb15 | |||
| b293310969 | |||
| a980397640 | |||
| 7c82e8b588 | |||
| 53cdca21fc | |||
| ba878d4d08 | |||
| f3a3acc747 | |||
| a6389174d7 | |||
| 2be4afdcb1 | |||
| aa85e05f79 | |||
| c2acb97a37 | |||
| 250b216a54 | |||
| 5f05d1f7f0 | |||
| 3d8d8568a3 | |||
| ccdd7730b2 | |||
| a474aebb94 | |||
| 29b65aeebf | |||
| 18beb09b42 | |||
| 54af104f8a | |||
| 8c5602b809 | |||
| 99f058a92f | |||
| 821a211f58 | |||
| 333b1d2512 | |||
| 391430de46 | |||
| ce79d7c240 | |||
| d09a566903 | |||
| 60b0befa60 | |||
| 3287f4c287 | |||
| 76fb14ed0c | |||
| 1d2153b253 | |||
| 005b798688 | |||
| b6d4c0e6a6 | |||
| 3854db9330 | |||
| fc181bda7c | |||
| fb822d20b6 | |||
| 0e708dde0f | |||
| 9a915c55d2 | |||
| 6887f85e6a | |||
| bb95e8ccbd | |||
| 41a4a94255 | |||
| 88527d5bb6 | |||
| 40441c929f | |||
| d3c4f04e07 | |||
| 4fc2c90702 | |||
| 2227aaa99f | |||
| 7cddc7abe3 | |||
| 59482ca712 | |||
| df5313a243 |
42
.gitignore
vendored
@@ -40,3 +40,45 @@ app-example
|
||||
.qodo
|
||||
|
||||
.env
|
||||
|
||||
# @generated expo-cli sync-8d4afeec25ea8a192358fae2f8e2fc766bdce4ec
|
||||
# The following patterns were generated by expo-cli
|
||||
|
||||
# Learn more https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files
|
||||
|
||||
# dependencies
|
||||
node_modules/
|
||||
|
||||
# Expo
|
||||
.expo/
|
||||
dist/
|
||||
web-build/
|
||||
expo-env.d.ts
|
||||
|
||||
# Native
|
||||
*.orig.*
|
||||
*.jks
|
||||
*.p8
|
||||
*.p12
|
||||
*.key
|
||||
*.mobileprovision
|
||||
|
||||
# Metro
|
||||
.metro-health-check*
|
||||
|
||||
# debug
|
||||
npm-debug.*
|
||||
yarn-debug.*
|
||||
yarn-error.*
|
||||
|
||||
# macOS
|
||||
.DS_Store
|
||||
*.pem
|
||||
|
||||
# local env files
|
||||
.env*.local
|
||||
|
||||
# typescript
|
||||
*.tsbuildinfo
|
||||
|
||||
# @end expo-cli
|
||||
169
QWEN.md
Normal file
@@ -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.
|
||||
@@ -64,9 +64,9 @@ react {
|
||||
}
|
||||
|
||||
/**
|
||||
* Set this to true to Run Proguard on Release builds to minify the Java bytecode.
|
||||
* Set this to true in release builds to optimize the app using [R8](https://developer.android.com/topic/performance/app-optimization/enable-app-optimization).
|
||||
*/
|
||||
def enableProguardInReleaseBuilds = (findProperty('android.enableProguardInReleaseBuilds') ?: false).toBoolean()
|
||||
def enableMinifyInReleaseBuilds = (findProperty('android.enableMinifyInReleaseBuilds') ?: false).toBoolean()
|
||||
|
||||
/**
|
||||
* The preferred build flavor of JavaScriptCore (JSC)
|
||||
@@ -82,6 +82,14 @@ def enableProguardInReleaseBuilds = (findProperty('android.enableProguardInRelea
|
||||
def jscFlavor = 'io.github.react-native-community:jsc-android:2026004.+'
|
||||
|
||||
android {
|
||||
// @generated begin @rnmapbox/maps-libcpp - expo prebuild (DO NOT MODIFY) sync-e24830a5a3e854b398227dfe9630aabfaa1cadd1
|
||||
packagingOptions {
|
||||
pickFirst 'lib/x86/libc++_shared.so'
|
||||
pickFirst 'lib/x86_64/libc++_shared.so'
|
||||
pickFirst 'lib/arm64-v8a/libc++_shared.so'
|
||||
pickFirst 'lib/armeabi-v7a/libc++_shared.so'
|
||||
}
|
||||
// @generated end @rnmapbox/maps-libcpp
|
||||
ndkVersion rootProject.ext.ndkVersion
|
||||
|
||||
buildToolsVersion rootProject.ext.buildToolsVersion
|
||||
@@ -92,8 +100,10 @@ android {
|
||||
applicationId 'com.bip.hipmimobileapp'
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
versionCode 1
|
||||
versionName "1.0.0"
|
||||
versionCode 4
|
||||
versionName "1.0.1"
|
||||
|
||||
buildConfigField "String", "REACT_NATIVE_RELEASE_LEVEL", "\"${findProperty('reactNativeReleaseLevel') ?: 'stable'}\""
|
||||
}
|
||||
signingConfigs {
|
||||
debug {
|
||||
@@ -111,15 +121,18 @@ android {
|
||||
// Caution! In production, you need to generate your own keystore file.
|
||||
// see https://reactnative.dev/docs/signed-apk-android.
|
||||
signingConfig signingConfigs.debug
|
||||
shrinkResources (findProperty('android.enableShrinkResourcesInReleaseBuilds')?.toBoolean() ?: false)
|
||||
minifyEnabled enableProguardInReleaseBuilds
|
||||
def enableShrinkResources = findProperty('android.enableShrinkResourcesInReleaseBuilds') ?: 'false'
|
||||
shrinkResources enableShrinkResources.toBoolean()
|
||||
minifyEnabled enableMinifyInReleaseBuilds
|
||||
proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
|
||||
crunchPngs (findProperty('android.enablePngCrunchInReleaseBuilds')?.toBoolean() ?: true)
|
||||
def enablePngCrunchInRelease = findProperty('android.enablePngCrunchInReleaseBuilds') ?: 'true'
|
||||
crunchPngs enablePngCrunchInRelease.toBoolean()
|
||||
}
|
||||
}
|
||||
packagingOptions {
|
||||
jniLibs {
|
||||
useLegacyPackaging (findProperty('expo.useLegacyPackaging')?.toBoolean() ?: false)
|
||||
def enableLegacyPackaging = findProperty('expo.useLegacyPackaging') ?: 'false'
|
||||
useLegacyPackaging enableLegacyPackaging.toBoolean()
|
||||
}
|
||||
}
|
||||
androidResources {
|
||||
@@ -175,3 +188,5 @@ dependencies {
|
||||
implementation jscFlavor
|
||||
}
|
||||
}
|
||||
|
||||
apply plugin: 'com.google.gms.google-services'
|
||||
29
android/app/google-services.json
Normal file
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"project_info": {
|
||||
"project_number": "608461535079",
|
||||
"project_id": "hipmi-badung-connect",
|
||||
"storage_bucket": "hipmi-badung-connect.firebasestorage.app"
|
||||
},
|
||||
"client": [
|
||||
{
|
||||
"client_info": {
|
||||
"mobilesdk_app_id": "1:608461535079:android:4ff12ddc283fb3746761c2",
|
||||
"android_client_info": {
|
||||
"package_name": "com.bip.hipmimobileapp"
|
||||
}
|
||||
},
|
||||
"oauth_client": [],
|
||||
"api_key": [
|
||||
{
|
||||
"current_key": "AIzaSyBiDtIk3Q9zffFwIdJ5cjqY7e4390JGSkM"
|
||||
}
|
||||
],
|
||||
"services": {
|
||||
"appinvite_service": {
|
||||
"other_platform_oauth_client": []
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"configuration_version": "1"
|
||||
}
|
||||
@@ -1,4 +1,6 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools">
|
||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
|
||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
|
||||
<uses-permission android:name="android.permission.CAMERA"/>
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
||||
@@ -13,7 +15,11 @@
|
||||
<data android:scheme="https"/>
|
||||
</intent>
|
||||
</queries>
|
||||
<application android:name=".MainApplication" android:label="@string/app_name" android:icon="@mipmap/ic_launcher" android:roundIcon="@mipmap/ic_launcher_round" android:allowBackup="true" android:theme="@style/AppTheme" android:supportsRtl="true">
|
||||
<application android:name=".MainApplication" android:label="@string/app_name" android:icon="@mipmap/ic_launcher" android:roundIcon="@mipmap/ic_launcher_round" android:allowBackup="true" android:theme="@style/AppTheme" android:supportsRtl="true" android:enableOnBackInvokedCallback="false" android:fullBackupContent="@xml/secure_store_backup_rules" android:dataExtractionRules="@xml/secure_store_data_extraction_rules">
|
||||
<meta-data android:name="com.google.firebase.messaging.default_notification_color" android:resource="@color/notification_icon_color" tools:replace="android:resource"/>
|
||||
<meta-data android:name="com.google.firebase.messaging.default_notification_icon" android:resource="@drawable/notification_icon"/>
|
||||
<meta-data android:name="expo.modules.notifications.default_notification_color" android:resource="@color/notification_icon_color"/>
|
||||
<meta-data android:name="expo.modules.notifications.default_notification_icon" android:resource="@drawable/notification_icon"/>
|
||||
<meta-data android:name="expo.modules.updates.ENABLED" android:value="false"/>
|
||||
<meta-data android:name="expo.modules.updates.EXPO_UPDATES_CHECK_ON_LAUNCH" android:value="ALWAYS"/>
|
||||
<meta-data android:name="expo.modules.updates.EXPO_UPDATES_LAUNCH_WAIT_MS" android:value="0"/>
|
||||
@@ -29,6 +35,12 @@
|
||||
<data android:scheme="hipmimobile"/>
|
||||
<data android:scheme="exp+hipmi-mobile"/>
|
||||
</intent-filter>
|
||||
<intent-filter android:autoVerify="true" data-generated="true">
|
||||
<action android:name="android.intent.action.VIEW"/>
|
||||
<data android:scheme="https" android:host="cld-dkr-staging-hipmi.wibudev.com" android:pathPrefix="/"/>
|
||||
<category android:name="android.intent.category.BROWSABLE"/>
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
</manifest>
|
||||
@@ -5,13 +5,13 @@ import android.content.res.Configuration
|
||||
|
||||
import com.facebook.react.PackageList
|
||||
import com.facebook.react.ReactApplication
|
||||
import com.facebook.react.ReactNativeApplicationEntryPoint.loadReactNative
|
||||
import com.facebook.react.ReactNativeHost
|
||||
import com.facebook.react.ReactPackage
|
||||
import com.facebook.react.ReactHost
|
||||
import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.load
|
||||
import com.facebook.react.common.ReleaseLevel
|
||||
import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint
|
||||
import com.facebook.react.defaults.DefaultReactNativeHost
|
||||
import com.facebook.react.soloader.OpenSourceMergedSoMapping
|
||||
import com.facebook.soloader.SoLoader
|
||||
|
||||
import expo.modules.ApplicationLifecycleDispatcher
|
||||
import expo.modules.ReactNativeHostWrapper
|
||||
@@ -19,21 +19,19 @@ import expo.modules.ReactNativeHostWrapper
|
||||
class MainApplication : Application(), ReactApplication {
|
||||
|
||||
override val reactNativeHost: ReactNativeHost = ReactNativeHostWrapper(
|
||||
this,
|
||||
object : DefaultReactNativeHost(this) {
|
||||
override fun getPackages(): List<ReactPackage> {
|
||||
val packages = PackageList(this).packages
|
||||
// Packages that cannot be autolinked yet can be added manually here, for example:
|
||||
// packages.add(MyReactNativePackage())
|
||||
return packages
|
||||
}
|
||||
this,
|
||||
object : DefaultReactNativeHost(this) {
|
||||
override fun getPackages(): List<ReactPackage> =
|
||||
PackageList(this).packages.apply {
|
||||
// Packages that cannot be autolinked yet can be added manually here, for example:
|
||||
// add(MyReactNativePackage())
|
||||
}
|
||||
|
||||
override fun getJSMainModuleName(): String = ".expo/.virtual-metro-entry"
|
||||
|
||||
override fun getUseDeveloperSupport(): Boolean = BuildConfig.DEBUG
|
||||
|
||||
override val isNewArchEnabled: Boolean = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED
|
||||
override val isHermesEnabled: Boolean = BuildConfig.IS_HERMES_ENABLED
|
||||
}
|
||||
)
|
||||
|
||||
@@ -42,11 +40,12 @@ class MainApplication : Application(), ReactApplication {
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
SoLoader.init(this, OpenSourceMergedSoMapping)
|
||||
if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) {
|
||||
// If you opted-in for the New Architecture, we load the native entry point for this app.
|
||||
load()
|
||||
DefaultNewArchitectureEntryPoint.releaseLevel = try {
|
||||
ReleaseLevel.valueOf(BuildConfig.REACT_NATIVE_RELEASE_LEVEL.uppercase())
|
||||
} catch (e: IllegalArgumentException) {
|
||||
ReleaseLevel.STABLE
|
||||
}
|
||||
loadReactNative(this)
|
||||
ApplicationLifecycleDispatcher.onApplicationCreate(this)
|
||||
}
|
||||
|
||||
|
||||
BIN
android/app/src/main/res/drawable-hdpi/notification_icon.png
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 55 KiB |
BIN
android/app/src/main/res/drawable-mdpi/notification_icon.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 30 KiB |
BIN
android/app/src/main/res/drawable-xhdpi/notification_icon.png
Normal file
|
After Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 84 KiB |
BIN
android/app/src/main/res/drawable-xxhdpi/notification_icon.png
Normal file
|
After Width: | Height: | Size: 6.2 KiB |
|
Before Width: | Height: | Size: 97 KiB After Width: | Height: | Size: 154 KiB |
BIN
android/app/src/main/res/drawable-xxxhdpi/notification_icon.png
Normal file
|
After Width: | Height: | Size: 9.6 KiB |
|
Before Width: | Height: | Size: 151 KiB After Width: | Height: | Size: 231 KiB |
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 6.7 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 7.3 KiB |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 3.8 KiB |
|
Before Width: | Height: | Size: 6.3 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 4.1 KiB |
|
Before Width: | Height: | Size: 4.3 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 5.1 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 8.0 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 58 KiB |
|
Before Width: | Height: | Size: 9.3 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 89 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 30 KiB |
@@ -3,4 +3,5 @@
|
||||
<color name="iconBackground">#ffffff</color>
|
||||
<color name="colorPrimary">#023c69</color>
|
||||
<color name="colorPrimaryDark">#ffffff</color>
|
||||
<color name="notification_icon_color">#ffffff</color>
|
||||
</resources>
|
||||
@@ -1,5 +1,5 @@
|
||||
<resources>
|
||||
<string name="app_name">HIPMI BADUNG</string>
|
||||
<string name="app_name">HIPMI Badung Connect</string>
|
||||
<string name="expo_system_ui_user_interface_style" translatable="false">automatic</string>
|
||||
<string name="expo_splash_screen_resize_mode" translatable="false">contain</string>
|
||||
<string name="expo_splash_screen_status_bar_translucent" translatable="false">false</string>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
<style name="AppTheme" parent="Theme.EdgeToEdge">
|
||||
<style name="AppTheme" parent="Theme.AppCompat.DayNight.NoActionBar">
|
||||
<item name="android:enforceNavigationBarContrast" tools:targetApi="29">true</item>
|
||||
<item name="android:editTextBackground">@drawable/rn_edit_text_material</item>
|
||||
<item name="colorPrimary">@color/colorPrimary</item>
|
||||
<item name="android:statusBarColor">#ffffff</item>
|
||||
@@ -8,5 +9,6 @@
|
||||
<item name="windowSplashScreenBackground">@color/splashscreen_background</item>
|
||||
<item name="windowSplashScreenAnimatedIcon">@drawable/splashscreen_logo</item>
|
||||
<item name="postSplashScreenTheme">@style/AppTheme</item>
|
||||
<item name="android:windowSplashScreenBehavior">icon_preferred</item>
|
||||
</style>
|
||||
</resources>
|
||||
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<full-backup-content>
|
||||
<exclude domain="sharedpref" path="SECURESTORE"/>
|
||||
</full-backup-content>
|
||||
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<data-extraction-rules>
|
||||
<cloud-backup>
|
||||
<exclude domain="sharedpref" path="SECURESTORE"/>
|
||||
</cloud-backup>
|
||||
<device-transfer>
|
||||
<exclude domain="sharedpref" path="SECURESTORE"/>
|
||||
</device-transfer>
|
||||
</data-extraction-rules>
|
||||
@@ -6,27 +6,15 @@ buildscript {
|
||||
mavenCentral()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.google.gms:google-services:4.4.1'
|
||||
classpath('com.android.tools.build:gradle')
|
||||
classpath('com.facebook.react:react-native-gradle-plugin')
|
||||
classpath('org.jetbrains.kotlin:kotlin-gradle-plugin')
|
||||
}
|
||||
}
|
||||
|
||||
def reactNativeAndroidDir = new File(
|
||||
providers.exec {
|
||||
workingDir(rootDir)
|
||||
commandLine("node", "--print", "require.resolve('react-native/package.json')")
|
||||
}.standardOutput.asText.get().trim(),
|
||||
"../android"
|
||||
)
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
maven {
|
||||
// All of React Native (JS, Obj-C sources, Android binaries) is installed from npm
|
||||
url(reactNativeAndroidDir)
|
||||
}
|
||||
|
||||
google()
|
||||
mavenCentral()
|
||||
maven { url 'https://www.jitpack.io' }
|
||||
@@ -35,3 +23,25 @@ allprojects {
|
||||
|
||||
apply plugin: "expo-root-project"
|
||||
apply plugin: "com.facebook.react.rootproject"
|
||||
// @generated begin @rnmapbox/maps-v2-maven - expo prebuild (DO NOT MODIFY) sync-d4ccbfdff48fdba3138b02a8ba41b9722af001d8
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
maven {
|
||||
url 'https://api.mapbox.com/downloads/v2/releases/maven'
|
||||
// Authentication is no longer required as per Mapbox's removal of download token requirement
|
||||
// See: https://github.com/mapbox/mapbox-maps-flutter/issues/775
|
||||
// Keeping this as optional for backward compatibility
|
||||
def token = project.properties['MAPBOX_DOWNLOADS_TOKEN'] ?: System.getenv('RNMAPBOX_MAPS_DOWNLOAD_TOKEN')
|
||||
if (token) {
|
||||
authentication { basic(BasicAuthentication) }
|
||||
credentials {
|
||||
username = 'mapbox'
|
||||
password = token
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// @generated end @rnmapbox/maps-v2-maven
|
||||
@@ -15,7 +15,7 @@ org.gradle.jvmargs=-Xmx2048m -XX:MaxMetaspaceSize=512m
|
||||
# When configured, Gradle will run in incubating parallel mode.
|
||||
# This option should only be used with decoupled projects. More details, visit
|
||||
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
|
||||
# org.gradle.parallel=true
|
||||
org.gradle.parallel=true
|
||||
|
||||
# AndroidX package structure to make it clearer which packages are bundled with the
|
||||
# Android operating system, and which are packaged with your app's APK
|
||||
@@ -41,6 +41,11 @@ newArchEnabled=true
|
||||
# If set to false, you will be using JSC instead.
|
||||
hermesEnabled=true
|
||||
|
||||
# Use this property to enable edge-to-edge display support.
|
||||
# This allows your app to draw behind system bars for an immersive UI.
|
||||
# Note: Only works with ReactActivity and should not be used with custom Activity.
|
||||
edgeToEdgeEnabled=true
|
||||
|
||||
# Enable GIF support in React Native images (~200 B increase)
|
||||
expo.gif.enabled=true
|
||||
# Enable webp support in React Native images (~85 KB increase)
|
||||
@@ -55,5 +60,6 @@ EX_DEV_CLIENT_NETWORK_INSPECTOR=true
|
||||
# Use legacy packaging to compress native libraries in the resulting APK.
|
||||
expo.useLegacyPackaging=false
|
||||
|
||||
# Whether the app is configured to use edge-to-edge via the app config or `react-native-edge-to-edge` plugin
|
||||
expo.edgeToEdgeEnabled=true
|
||||
# Specifies whether the app is configured to use edge-to-edge via the app config or plugin
|
||||
# WARNING: This property has been deprecated and will be removed in Expo SDK 55. Use `edgeToEdgeEnabled` or `react.edgeToEdgeEnabled` to determine whether the project is using edge-to-edge.
|
||||
expo.edgeToEdgeEnabled=true
|
||||
|
||||
BIN
android/gradle/wrapper/gradle-wrapper.jar
vendored
@@ -1,6 +1,6 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
|
||||
4
android/gradlew
vendored
@@ -114,7 +114,7 @@ case "$( uname )" in #(
|
||||
NONSTOP* ) nonstop=true ;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
CLASSPATH="\\\"\\\""
|
||||
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
@@ -213,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
set -- \
|
||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||
-classpath "$CLASSPATH" \
|
||||
org.gradle.wrapper.GradleWrapperMain \
|
||||
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
|
||||
"$@"
|
||||
|
||||
# Stop when "xargs" is not available.
|
||||
|
||||
4
android/gradlew.bat
vendored
@@ -70,11 +70,11 @@ goto fail
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
set CLASSPATH=
|
||||
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
|
||||
@@ -31,7 +31,7 @@ extensions.configure(com.facebook.react.ReactSettingsExtension) { ex ->
|
||||
}
|
||||
expoAutolinking.useExpoModules()
|
||||
|
||||
rootProject.name = 'HIPMI BADUNG'
|
||||
rootProject.name = 'HIPMI Badung Connect'
|
||||
|
||||
expoAutolinking.useExpoVersionCatalog()
|
||||
|
||||
|
||||
@@ -1,59 +1,92 @@
|
||||
// app.config.js
|
||||
require('dotenv').config();
|
||||
require("dotenv").config();
|
||||
|
||||
export default {
|
||||
name: 'HIPMI BADUNG',
|
||||
slug: 'hipmi-mobile',
|
||||
version: '1.0.0',
|
||||
orientation: 'portrait',
|
||||
icon: './assets/images/icon.png',
|
||||
scheme: 'hipmimobile',
|
||||
userInterfaceStyle: 'automatic',
|
||||
name: "HIPMI Badung Connect",
|
||||
slug: "hipmi-mobile",
|
||||
version: "1.0.1",
|
||||
orientation: "portrait",
|
||||
icon: "./assets/images/icon.png",
|
||||
scheme: "hipmimobile",
|
||||
userInterfaceStyle: "automatic",
|
||||
newArchEnabled: true,
|
||||
|
||||
ios: {
|
||||
supportsTablet: true,
|
||||
bundleIdentifier: 'com.anonymous.hipmi-mobile',
|
||||
bundleIdentifier: "com.anonymous.hipmi-mobile",
|
||||
googleServicesFile: "./ios/HIPMIBadungConnect/GoogleService-Info.plist",
|
||||
infoPlist: {
|
||||
ITSAppUsesNonExemptEncryption: false,
|
||||
NSLocationWhenInUseUsageDescription:
|
||||
"Aplikasi membutuhkan akses lokasi untuk menampilkan peta.",
|
||||
},
|
||||
associatedDomains: ["applinks:cld-dkr-staging-hipmi.wibudev.com"],
|
||||
buildNumber: "20",
|
||||
},
|
||||
|
||||
android: {
|
||||
googleServicesFile: "./google-services.json",
|
||||
adaptiveIcon: {
|
||||
foregroundImage: './assets/images/splash-icon.png',
|
||||
backgroundColor: '#ffffff',
|
||||
foregroundImage: "./assets/images/splash-icon.png",
|
||||
backgroundColor: "#ffffff",
|
||||
},
|
||||
edgeToEdgeEnabled: true,
|
||||
package: 'com.bip.hipmimobileapp',
|
||||
package: "com.bip.hipmimobileapp",
|
||||
versionCode: 4,
|
||||
// softwareKeyboardLayoutMode: 'resize', // option: untuk mengatur keyboard pada room chst collaboration
|
||||
intentFilters: [
|
||||
{
|
||||
action: "VIEW",
|
||||
autoVerify: true, // wajib untuk App Links
|
||||
data: [
|
||||
{
|
||||
scheme: "https",
|
||||
host: "cld-dkr-staging-hipmi.wibudev.com",
|
||||
pathPrefix: "/",
|
||||
},
|
||||
],
|
||||
category: ["BROWSABLE", "DEFAULT"],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
web: {
|
||||
bundler: 'metro',
|
||||
output: 'static',
|
||||
favicon: './assets/images/favicon.png',
|
||||
bundler: "metro",
|
||||
output: "static",
|
||||
favicon: "./assets/images/favicon.png",
|
||||
},
|
||||
|
||||
plugins: [
|
||||
'expo-router',
|
||||
"expo-router",
|
||||
"expo-web-browser",
|
||||
[
|
||||
'expo-splash-screen',
|
||||
"expo-splash-screen",
|
||||
{
|
||||
image: './assets/images/splash-icon.png',
|
||||
image: "./assets/images/splash-icon.png",
|
||||
imageWidth: 200,
|
||||
resizeMode: 'contain',
|
||||
backgroundColor: '#ffffff',
|
||||
resizeMode: "contain",
|
||||
backgroundColor: "#ffffff",
|
||||
},
|
||||
],
|
||||
[
|
||||
'expo-camera',
|
||||
"expo-camera",
|
||||
{
|
||||
cameraPermission: 'Allow $(PRODUCT_NAME) to access your camera',
|
||||
microphonePermission: 'Allow $(PRODUCT_NAME) to access your microphone',
|
||||
cameraPermission: "Allow $(PRODUCT_NAME) to access your camera",
|
||||
microphonePermission: "Allow $(PRODUCT_NAME) to access your microphone",
|
||||
recordAudioAndroid: true,
|
||||
},
|
||||
],
|
||||
'expo-font',
|
||||
"expo-font",
|
||||
"@rnmapbox/maps",
|
||||
"@react-native-firebase/app",
|
||||
[
|
||||
"expo-notifications",
|
||||
{
|
||||
icon: "./assets/images/icon.png",
|
||||
color: "#ffffff",
|
||||
iosDisplayInForeground: true,
|
||||
},
|
||||
],
|
||||
],
|
||||
|
||||
experiments: {
|
||||
@@ -63,9 +96,11 @@ export default {
|
||||
extra: {
|
||||
router: {},
|
||||
eas: {
|
||||
projectId: '5cf15964-4889-4755-b8ed-b99c61d614d1',
|
||||
projectId: "5cf15964-4889-4755-b8ed-b99c61d614d1",
|
||||
},
|
||||
// Tambahkan environment variables ke sini
|
||||
API_BASE_URL: process.env.API_BASE_URL,
|
||||
BASE_URL: process.env.BASE_URL,
|
||||
DEEP_LINK_URL: process.env.DEEP_LINK_URL,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
import { BackButton, ViewWrapper } from "@/components";
|
||||
import { MainColor } from "@/constants/color-palet";
|
||||
import { FontAwesome } from "@expo/vector-icons";
|
||||
import { Stack } from "expo-router";
|
||||
import { BackButton } from "@/components";
|
||||
import PdfViewer from "@/components/_ShareComponent/PdfViewer";
|
||||
import API_STRORAGE from "@/constants/base-url-api-strorage";
|
||||
import { Stack, useLocalSearchParams } from "expo-router";
|
||||
import { SafeAreaView } from "react-native-safe-area-context";
|
||||
|
||||
export default function FileScreen() {
|
||||
const { id } = useLocalSearchParams();
|
||||
const url = API_STRORAGE.GET({ fileId: id as string });
|
||||
|
||||
return (
|
||||
<>
|
||||
<Stack.Screen
|
||||
@@ -12,14 +16,9 @@ export default function FileScreen() {
|
||||
headerLeft: () => <BackButton />,
|
||||
}}
|
||||
/>
|
||||
<ViewWrapper>
|
||||
<FontAwesome
|
||||
name="file-pdf-o"
|
||||
size={300}
|
||||
style={{ alignSelf: "center" }}
|
||||
color={MainColor.white}
|
||||
/>
|
||||
</ViewWrapper>
|
||||
<SafeAreaView style={{ flex: 1 }} edges={["bottom"]}>
|
||||
<PdfViewer uri={url} />
|
||||
</SafeAreaView>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,9 +1,65 @@
|
||||
import { LandscapeFrameUploaded, ViewWrapper } from "@/components";
|
||||
import { CenterCustom, TextCustom, ViewWrapper } from "@/components";
|
||||
import API_STRORAGE from "@/constants/base-url-api-strorage";
|
||||
import { MainColor } from "@/constants/color-palet";
|
||||
import { Image } from "expo-image";
|
||||
import { useLocalSearchParams } from "expo-router";
|
||||
import React, { useState } from "react";
|
||||
import { View } from "react-native";
|
||||
|
||||
export default function PreviewImage() {
|
||||
const { id } = useLocalSearchParams();
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
|
||||
return (
|
||||
<ViewWrapper>
|
||||
<LandscapeFrameUploaded />
|
||||
{id ? (
|
||||
<View
|
||||
style={{
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
position: "relative",
|
||||
}}
|
||||
>
|
||||
{/* Main Image */}
|
||||
<Image
|
||||
onLoad={() => {
|
||||
setIsLoading(false);
|
||||
}}
|
||||
source={API_STRORAGE.GET({ fileId: id as string })}
|
||||
contentFit="contain"
|
||||
style={{ width: "100%", height: "100%" }}
|
||||
// placeholder={require("@/assets/images/loading.gif")}
|
||||
/>
|
||||
|
||||
{/* Custom Loader Overlay */}
|
||||
{isLoading && (
|
||||
<View
|
||||
style={{
|
||||
position: "absolute",
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
backgroundColor: MainColor.darkblue,
|
||||
zIndex: 1,
|
||||
opacity: 0.5,
|
||||
}}
|
||||
>
|
||||
<Image
|
||||
source={require("@/assets/images/loading.gif")}
|
||||
contentFit="contain"
|
||||
style={{ width: 60, height: 60 }}
|
||||
/>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
) : (
|
||||
<CenterCustom>
|
||||
<TextCustom>File not found</TextCustom>
|
||||
</CenterCustom>
|
||||
)}
|
||||
</ViewWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { BackButton } from "@/components";
|
||||
import { IconPlus } from "@/components/_Icon";
|
||||
import { IconDot } from "@/components/_Icon/IconComponent";
|
||||
import LeftButtonCustom from "@/components/Button/BackButton";
|
||||
import { MainColor } from "@/constants/color-palet";
|
||||
import { ICON_SIZE_SMALL } from "@/constants/constans-value";
|
||||
@@ -10,7 +12,13 @@ export default function UserLayout() {
|
||||
return (
|
||||
<>
|
||||
<Stack screenOptions={HeaderStyles}>
|
||||
|
||||
<Stack.Screen
|
||||
name="delete-account"
|
||||
options={{
|
||||
title: "Hapus Akun",
|
||||
headerLeft: () => <BackButton />,
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="waiting-room"
|
||||
options={{
|
||||
@@ -45,24 +53,35 @@ export default function UserLayout() {
|
||||
/>
|
||||
|
||||
{/* ========== Notification Section ========= */}
|
||||
<Stack.Screen
|
||||
|
||||
{/* DIPINDAH DI FILE NOTIFICATION USER */}
|
||||
{/* <Stack.Screen
|
||||
name="notifications/index"
|
||||
options={{
|
||||
title: "Notifikasi",
|
||||
headerLeft: () => <BackButton />,
|
||||
// headerRight: () => (
|
||||
// <IconPlus
|
||||
// color={MainColor.yellow}
|
||||
// onPress={() => router.push("/test-notifications")}
|
||||
// />
|
||||
// ),
|
||||
}}
|
||||
/>
|
||||
/> */}
|
||||
|
||||
{/* ========== Event Section ========= */}
|
||||
|
||||
<Stack.Screen
|
||||
name="event/(tabs)"
|
||||
options={{
|
||||
title: "Event",
|
||||
headerLeft: () => (
|
||||
<LeftButtonCustom path="/(application)/(user)/home" />
|
||||
),
|
||||
// NOTE: DIPINDAH DI FILE /Event/(Tabs)/_layout.tsx
|
||||
// headerLeft: () => (
|
||||
// <LeftButtonCustom path="/(application)/(user)/home" />
|
||||
// ),
|
||||
}}
|
||||
/>
|
||||
|
||||
<Stack.Screen
|
||||
name="event/create"
|
||||
options={{
|
||||
@@ -118,14 +137,13 @@ export default function UserLayout() {
|
||||
headerLeft: () => <BackButton />,
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
{/* <Stack.Screen
|
||||
name="collaboration/[id]/detail-participant"
|
||||
options={{
|
||||
title: "Partisipasi Proyek",
|
||||
headerLeft: () => <BackButton />,
|
||||
}}
|
||||
/>
|
||||
|
||||
/> */}
|
||||
<Stack.Screen
|
||||
name="collaboration/[id]/edit"
|
||||
options={{
|
||||
@@ -133,6 +151,20 @@ export default function UserLayout() {
|
||||
headerLeft: () => <BackButton />,
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="collaboration/[id]/create-pacticipants"
|
||||
options={{
|
||||
title: "Ajukan Partisipasi",
|
||||
headerLeft: () => <BackButton />,
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="collaboration/[id]/select-of-participants"
|
||||
options={{
|
||||
title: "Pilih Partisipan",
|
||||
headerLeft: () => <BackButton />,
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* ========== End Collaboration Section ========= */}
|
||||
|
||||
@@ -173,7 +205,7 @@ export default function UserLayout() {
|
||||
name="crowdfunding/index"
|
||||
options={{
|
||||
title: "Crowdfunding",
|
||||
headerLeft: () => <BackButton />,
|
||||
headerLeft: () => <BackButton path="/home" />,
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -436,7 +468,7 @@ export default function UserLayout() {
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="donation/[id]/(transaction-flow)/[transaction]/invoice"
|
||||
name="donation/[id]/(transaction-flow)/[invoiceId]/invoice"
|
||||
options={{
|
||||
title: "Invoice",
|
||||
headerLeft: () => (
|
||||
@@ -450,7 +482,7 @@ export default function UserLayout() {
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="donation/[id]/(transaction-flow)/[transaction]/process"
|
||||
name="donation/[id]/(transaction-flow)/[invoiceId]/process"
|
||||
options={{
|
||||
title: "Proses",
|
||||
headerLeft: () => (
|
||||
@@ -464,14 +496,14 @@ export default function UserLayout() {
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="donation/[id]/(transaction-flow)/[transaction]/success"
|
||||
name="donation/[id]/(transaction-flow)/[invoiceId]/success"
|
||||
options={{
|
||||
title: "Donasi Berhasil",
|
||||
headerLeft: () => <BackButton />,
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="donation/[id]/(transaction-flow)/[transaction]/failed"
|
||||
name="donation/[id]/(transaction-flow)/[invoiceId]/failed"
|
||||
options={{
|
||||
title: "Donasi Gagal",
|
||||
headerLeft: () => <BackButton />,
|
||||
@@ -492,7 +524,8 @@ export default function UserLayout() {
|
||||
name="job/(tabs)"
|
||||
options={{
|
||||
title: "Job Vacancy",
|
||||
headerLeft: () => <BackButton path="/home" />,
|
||||
// headerLeft: () => <BackButton path="/home" />,
|
||||
// NOTE: headerLeft di pindahkan ke Tabs Layout
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
@@ -509,6 +542,13 @@ export default function UserLayout() {
|
||||
headerLeft: () => <BackButton />,
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="job/[id]/archive"
|
||||
options={{
|
||||
title: "Arsip Job",
|
||||
headerLeft: () => <BackButton />,
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* ========== End Job Section ========= */}
|
||||
|
||||
@@ -569,6 +609,27 @@ export default function UserLayout() {
|
||||
headerLeft: () => <BackButton />,
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="forum/terms"
|
||||
options={{
|
||||
title: "Syarat & Ketentuan Forum",
|
||||
headerLeft: () => <BackButton />,
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="forum/[id]/preview-report-posting"
|
||||
options={{
|
||||
title: "Laporan Postingan",
|
||||
headerLeft: () => <BackButton />,
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="forum/[id]/preview-report-comment"
|
||||
options={{
|
||||
title: "Laporan Komentar",
|
||||
headerLeft: () => <BackButton />,
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* ========== Maps Section ========= */}
|
||||
<Stack.Screen
|
||||
|
||||
@@ -1,38 +1,96 @@
|
||||
import { BaseBox, Grid, TextCustom } from "@/components";
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
BaseBox,
|
||||
Grid,
|
||||
LoaderCustom,
|
||||
StackCustom,
|
||||
TextCustom,
|
||||
} from "@/components";
|
||||
import ViewWrapper from "@/components/_ShareComponent/ViewWrapper";
|
||||
import { MainColor } from "@/constants/color-palet";
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
import { apiCollaborationGetAll } from "@/service/api-client/api-collaboration";
|
||||
import { Feather } from "@expo/vector-icons";
|
||||
import { useFocusEffect } from "expo-router";
|
||||
import _ from "lodash";
|
||||
import { useState, useCallback } from "react";
|
||||
|
||||
export default function CollaborationGroup() {
|
||||
|
||||
const { user } = useAuth();
|
||||
const [listData, setListData] = useState<any[]>();
|
||||
const [loadingGetData, setLoadingGetData] = useState(false);
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
onLoadData();
|
||||
}, [user?.id])
|
||||
);
|
||||
|
||||
const onLoadData = async () => {
|
||||
try {
|
||||
setLoadingGetData(true);
|
||||
const response = await apiCollaborationGetAll({
|
||||
category: "group",
|
||||
authorId: user?.id,
|
||||
});
|
||||
|
||||
if (response.success) {
|
||||
setListData(response.data);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
} finally {
|
||||
setLoadingGetData(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<ViewWrapper hideFooter>
|
||||
{Array.from({ length: 10 }).map((_, index) => (
|
||||
<BaseBox
|
||||
key={index}
|
||||
paddingBlock={5}
|
||||
href={`/collaboration/${index}/${generateProjectName()}/room-chat`}
|
||||
>
|
||||
<Grid>
|
||||
<Grid.Col span={10}>
|
||||
<TextCustom bold>{generateProjectName()}</TextCustom>
|
||||
<TextCustom size="small">2 Anggota</TextCustom>
|
||||
</Grid.Col>
|
||||
<Grid.Col
|
||||
span={2}
|
||||
style={{ alignItems: "flex-end", justifyContent: "center" }}
|
||||
{loadingGetData ? (
|
||||
<LoaderCustom />
|
||||
) : _.isEmpty(listData) ? (
|
||||
<TextCustom align="center">Tidak ada data</TextCustom>
|
||||
) : (
|
||||
<StackCustom>
|
||||
{listData?.map((item: any, index: any) => (
|
||||
<BaseBox
|
||||
key={index}
|
||||
paddingBlock={5}
|
||||
href={`/collaboration/${item?.ProjectCollaboration_RoomChat?.id}/${item?.ProjectCollaboration_RoomChat?.name}/room-chat`}
|
||||
>
|
||||
<Feather name="chevron-right" size={20} color={MainColor.white} />
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
</BaseBox>
|
||||
))}
|
||||
<Grid>
|
||||
<Grid.Col span={10}>
|
||||
<TextCustom bold>
|
||||
{item?.ProjectCollaboration_RoomChat?.name}
|
||||
</TextCustom>
|
||||
<TextCustom size="small">
|
||||
{
|
||||
item?.ProjectCollaboration_RoomChat
|
||||
?.ProjectCollaboration_AnggotaRoomChat?.length
|
||||
}{" "}
|
||||
Anggota
|
||||
</TextCustom>
|
||||
</Grid.Col>
|
||||
<Grid.Col
|
||||
span={2}
|
||||
style={{ alignItems: "flex-end", justifyContent: "center" }}
|
||||
>
|
||||
<Feather
|
||||
name="chevron-right"
|
||||
size={20}
|
||||
color={MainColor.white}
|
||||
/>
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
</BaseBox>
|
||||
))}
|
||||
</StackCustom>
|
||||
)}
|
||||
</ViewWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
function generateProjectName() {
|
||||
const adjectives = [
|
||||
"Blue",
|
||||
@@ -65,4 +123,4 @@ function generateProjectName() {
|
||||
const randomNoun = nouns[Math.floor(Math.random() * nouns.length)];
|
||||
|
||||
return randomAdjective + randomNoun;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,40 @@
|
||||
import { FloatingButton, ViewWrapper } from "@/components";
|
||||
import {
|
||||
FloatingButton,
|
||||
LoaderCustom,
|
||||
TextCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import Collaboration_BoxPublishSection from "@/screens/Collaboration/BoxPublishSection";
|
||||
import { router } from "expo-router";
|
||||
import { apiCollaborationGetAll } from "@/service/api-client/api-collaboration";
|
||||
import { router, useFocusEffect } from "expo-router";
|
||||
import _ from "lodash";
|
||||
import { useCallback, useState } from "react";
|
||||
|
||||
export default function CollaborationBeranda() {
|
||||
const [listData, setListData] = useState<any[]>();
|
||||
const [loadingGetData, setLoadingGetData] = useState(false);
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
onLoadData();
|
||||
}, [])
|
||||
);
|
||||
|
||||
const onLoadData = async () => {
|
||||
try {
|
||||
setLoadingGetData(true);
|
||||
const response = await apiCollaborationGetAll({
|
||||
category: "beranda",
|
||||
});
|
||||
|
||||
setListData(response.data);
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
} finally {
|
||||
setLoadingGetData(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<ViewWrapper
|
||||
@@ -15,13 +47,19 @@ export default function CollaborationBeranda() {
|
||||
/>
|
||||
}
|
||||
>
|
||||
{Array.from({ length: 10 }).map((_, index) => (
|
||||
<Collaboration_BoxPublishSection
|
||||
key={index}
|
||||
id={index.toString()}
|
||||
href={`/collaboration/${index}`}
|
||||
/>
|
||||
))}
|
||||
{loadingGetData ? (
|
||||
<LoaderCustom />
|
||||
) : _.isEmpty(listData) ? (
|
||||
<TextCustom align="center">Tidak ada data</TextCustom>
|
||||
) : (
|
||||
listData?.map((item: any, index: number) => (
|
||||
<Collaboration_BoxPublishSection
|
||||
key={index}
|
||||
href={`/collaboration/${item.id}`}
|
||||
data={item}
|
||||
/>
|
||||
))
|
||||
)}
|
||||
</ViewWrapper>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -1,15 +1,46 @@
|
||||
import { ButtonCustom, Spacing } from "@/components";
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import { ButtonCustom, LoaderCustom, Spacing, TextCustom } from "@/components";
|
||||
import ViewWrapper from "@/components/_ShareComponent/ViewWrapper";
|
||||
import { AccentColor, MainColor } from "@/constants/color-palet";
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
import Collaboration_BoxPublishSection from "@/screens/Collaboration/BoxPublishSection";
|
||||
import { useState } from "react";
|
||||
import { apiCollaborationGetAll } from "@/service/api-client/api-collaboration";
|
||||
import { useFocusEffect } from "expo-router";
|
||||
import _ from "lodash";
|
||||
import { useCallback, useState } from "react";
|
||||
import { View } from "react-native";
|
||||
|
||||
export default function CollaborationParticipans() {
|
||||
const [activeCategory, setActiveCategory] = useState<string | null>(
|
||||
"participant"
|
||||
const [activeCategory, setActiveCategory] = useState<
|
||||
"participant" | "my-project"
|
||||
>("participant");
|
||||
const { user } = useAuth();
|
||||
const [listData, setListData] = useState<any[]>();
|
||||
const [loadingGetData, setLoadingGetData] = useState(false);
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
onLoadData();
|
||||
}, [activeCategory])
|
||||
);
|
||||
|
||||
const onLoadData = async () => {
|
||||
try {
|
||||
setLoadingGetData(true);
|
||||
const response = await apiCollaborationGetAll({
|
||||
category:
|
||||
activeCategory === "participant" ? "participant" : "my-project",
|
||||
authorId: user?.id,
|
||||
});
|
||||
|
||||
setListData(response.data);
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
} finally {
|
||||
setLoadingGetData(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handlePress = (item: any) => {
|
||||
setActiveCategory(item);
|
||||
// tambahkan logika lain seperti filter dsb.
|
||||
@@ -41,13 +72,13 @@ export default function CollaborationParticipans() {
|
||||
<Spacing width={"2%"} />
|
||||
<ButtonCustom
|
||||
backgroundColor={
|
||||
activeCategory === "main" ? MainColor.yellow : AccentColor.blue
|
||||
activeCategory === "my-project" ? MainColor.yellow : AccentColor.blue
|
||||
}
|
||||
textColor={
|
||||
activeCategory === "main" ? MainColor.black : MainColor.white
|
||||
activeCategory === "my-project" ? MainColor.black : MainColor.white
|
||||
}
|
||||
style={{ width: "49%" }}
|
||||
onPress={() => handlePress("main")}
|
||||
onPress={() => handlePress("my-project")}
|
||||
>
|
||||
Proyek Saya
|
||||
</ButtonCustom>
|
||||
@@ -56,22 +87,27 @@ export default function CollaborationParticipans() {
|
||||
|
||||
return (
|
||||
<ViewWrapper hideFooter headerComponent={headerComponent}>
|
||||
{Array.from({ length: 10 }).map((_, index) => (
|
||||
<Collaboration_BoxPublishSection
|
||||
key={index.toString()}
|
||||
id={index.toString()}
|
||||
username={` ${
|
||||
activeCategory === "participant"
|
||||
? "Partisipasi Proyek"
|
||||
: "Proyek Saya"
|
||||
}`}
|
||||
href={
|
||||
activeCategory === "participant"
|
||||
? `/collaboration/${index}/detail-participant`
|
||||
: `/collaboration/${index}/detail-project-main`
|
||||
}
|
||||
/>
|
||||
))}
|
||||
{loadingGetData ? (
|
||||
<LoaderCustom />
|
||||
) : _.isEmpty(listData) ? (
|
||||
<TextCustom align="center">Tidak ada data</TextCustom>
|
||||
) : activeCategory === "participant" ? (
|
||||
listData?.map((item: any, index: number) => (
|
||||
<Collaboration_BoxPublishSection
|
||||
key={index.toString()}
|
||||
data={item?.ProjectCollaboration}
|
||||
href={`/collaboration/${item?.ProjectCollaboration?.id}/detail-participant`}
|
||||
/>
|
||||
))
|
||||
) : (
|
||||
listData?.map((item: any, index: number) => (
|
||||
<Collaboration_BoxPublishSection
|
||||
key={index.toString()}
|
||||
data={item}
|
||||
href={`/collaboration/${item?.id}/detail-project-main`}
|
||||
/>
|
||||
))
|
||||
)}
|
||||
</ViewWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,17 +1,41 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
import {
|
||||
AvatarUsernameAndOtherComponent,
|
||||
BackButton,
|
||||
BaseBox,
|
||||
BoxWithHeaderSection,
|
||||
Grid,
|
||||
StackCustom,
|
||||
TextCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import { Stack, useLocalSearchParams } from "expo-router";
|
||||
import { apiCollaborationGroup } from "@/service/api-client/api-collaboration";
|
||||
import { Stack, useFocusEffect, useLocalSearchParams } from "expo-router";
|
||||
import { useState, useCallback } from "react";
|
||||
|
||||
export default function CollaborationRoomInfo() {
|
||||
const { id, detail } = useLocalSearchParams();
|
||||
const [data, setData] = useState<any>();
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
onLoadData();
|
||||
}, [id])
|
||||
);
|
||||
|
||||
const onLoadData = async () => {
|
||||
try {
|
||||
const response = await apiCollaborationGroup({ id: id as string });
|
||||
|
||||
if (response.success) {
|
||||
setData(response.data);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Stack.Screen
|
||||
@@ -24,7 +48,7 @@ export default function CollaborationRoomInfo() {
|
||||
<ViewWrapper>
|
||||
<BoxWithHeaderSection>
|
||||
<StackCustom>
|
||||
{listData.map((item, index) => (
|
||||
{listData({ data }).map((item, index) => (
|
||||
<Grid key={index}>
|
||||
<Grid.Col span={4}>
|
||||
<TextCustom bold>{item.title}</TextCustom>
|
||||
@@ -37,37 +61,42 @@ export default function CollaborationRoomInfo() {
|
||||
</StackCustom>
|
||||
</BoxWithHeaderSection>
|
||||
|
||||
<BoxWithHeaderSection>
|
||||
{Array.from({ length: 10 }).map((_, index) => (
|
||||
<AvatarUsernameAndOtherComponent key={index} avatarHref={`/profile/${index}`} />
|
||||
))}
|
||||
</BoxWithHeaderSection>
|
||||
<BaseBox>
|
||||
<StackCustom gap={10}>
|
||||
{data?.ProjectCollaboration_AnggotaRoomChat?.map(
|
||||
(item: any, index: number) => (
|
||||
<AvatarUsernameAndOtherComponent
|
||||
key={index}
|
||||
avatarHref={`/profile/${item?.User?.Profile?.id}`}
|
||||
name={item?.User?.username}
|
||||
avatar={item?.User?.Profile?.imageId}
|
||||
/>
|
||||
)
|
||||
)}
|
||||
</StackCustom>
|
||||
</BaseBox>
|
||||
</ViewWrapper>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const listData = [
|
||||
const listData = ({ data }: { data: any }) => [
|
||||
{
|
||||
title: "Judul Proyek",
|
||||
value: "Judul Proyek",
|
||||
value: data?.ProjectCollaboration?.title || "-",
|
||||
},
|
||||
{
|
||||
title: "Industri",
|
||||
value: "Pilihan Industri",
|
||||
},
|
||||
{
|
||||
title: "Deskripsi",
|
||||
value: "Deskripsi Proyek",
|
||||
value:
|
||||
data?.ProjectCollaboration?.ProjectCollaborationMaster_Industri?.name ||
|
||||
"-",
|
||||
},
|
||||
{
|
||||
title: "Tujuan Proyek",
|
||||
value:
|
||||
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
|
||||
value: data?.ProjectCollaboration?.purpose || "-",
|
||||
},
|
||||
{
|
||||
title: "Keuntungan Proyek",
|
||||
value:
|
||||
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
|
||||
value: data?.ProjectCollaboration?.benefit || "-",
|
||||
},
|
||||
];
|
||||
|
||||
@@ -1,68 +1,13 @@
|
||||
import {
|
||||
BackButton,
|
||||
BoxButtonOnFooter,
|
||||
Grid,
|
||||
TextCustom,
|
||||
TextInputCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import { AccentColor, MainColor } from "@/constants/color-palet";
|
||||
import { BackButton } from "@/components";
|
||||
import { MainColor } from "@/constants/color-palet";
|
||||
import { ICON_SIZE_SMALL } from "@/constants/constans-value";
|
||||
import ChatScreen from "@/screens/Collaboration/GroupChatSection";
|
||||
import { Feather } from "@expo/vector-icons";
|
||||
import { router, Stack, useLocalSearchParams } from "expo-router";
|
||||
import { StyleSheet, TouchableOpacity, View } from "react-native";
|
||||
|
||||
export default function CollaborationRoomChat() {
|
||||
const { id, detail } = useLocalSearchParams();
|
||||
|
||||
const inputChat = () => {
|
||||
return (
|
||||
<>
|
||||
<BoxButtonOnFooter>
|
||||
{/* <View style={{flexDirection: 'row', alignItems: 'center'}}>
|
||||
<TextInputCustom placeholder="Ketik pesan..." />
|
||||
<TouchableOpacity
|
||||
activeOpacity={0.5}
|
||||
onPress={() => console.log("Send")}
|
||||
style={{
|
||||
backgroundColor: AccentColor.blue,
|
||||
padding: 10,
|
||||
borderRadius: 50,
|
||||
}}
|
||||
>
|
||||
<Feather name="send" size={30} color={MainColor.white} />
|
||||
</TouchableOpacity>
|
||||
</View> */}
|
||||
<Grid>
|
||||
<Grid.Col span={9}>
|
||||
<TextInputCustom placeholder="Ketik pesan..." />
|
||||
</Grid.Col>
|
||||
<Grid.Col span={1}>
|
||||
<View />
|
||||
</Grid.Col>
|
||||
<Grid.Col span={2} style={{ alignItems: "center" }}>
|
||||
<TouchableOpacity
|
||||
activeOpacity={0.5}
|
||||
onPress={() => console.log("Send")}
|
||||
style={{
|
||||
backgroundColor: AccentColor.blue,
|
||||
padding: 10,
|
||||
borderRadius: 50,
|
||||
}}
|
||||
>
|
||||
<Feather
|
||||
name="send"
|
||||
size={30}
|
||||
color={MainColor.white}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
</BoxButtonOnFooter>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Stack.Screen
|
||||
@@ -79,114 +24,8 @@ export default function CollaborationRoomChat() {
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<ViewWrapper footerComponent={inputChat()}>
|
||||
{dummyData.map((item, index) => (
|
||||
<View
|
||||
key={index}
|
||||
style={[
|
||||
styles.messageRow,
|
||||
item.role === 1 ? styles.rightAlign : styles.leftAlign,
|
||||
]}
|
||||
>
|
||||
<View
|
||||
style={[
|
||||
styles.bubble,
|
||||
item.role === 1 ? styles.bubbleRight : styles.bubbleLeft,
|
||||
]}
|
||||
>
|
||||
<TextCustom style={styles.sender}>{item.nama}</TextCustom>
|
||||
<TextCustom style={styles.message}>{item.chat}</TextCustom>
|
||||
<TextCustom style={styles.time}>
|
||||
{new Date(item.time).toLocaleTimeString([], {
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
})}
|
||||
</TextCustom>
|
||||
</View>
|
||||
</View>
|
||||
))}
|
||||
{/* <TextInputCustom placeholder="Ketik pesan..." />
|
||||
<Spacing/> */}
|
||||
</ViewWrapper>
|
||||
|
||||
<ChatScreen id={id as string} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const dummyData = [
|
||||
{
|
||||
nama: "Dina",
|
||||
role: 1,
|
||||
chat: "Hai! Kamu udah lihat dokumen proyek yang baru?",
|
||||
time: "2025-07-24T09:01:15Z",
|
||||
},
|
||||
{
|
||||
nama: "Rafi",
|
||||
role: 2,
|
||||
chat: "Halo! Iya, aku baru aja baca. Kayaknya kita harus revisi bagian akhir deh.",
|
||||
time: "2025-07-24T09:02:03Z",
|
||||
},
|
||||
{
|
||||
nama: "Dina",
|
||||
role: 1,
|
||||
chat: "Setuju. Aku juga kurang sreg sama penutupnya.",
|
||||
time: "2025-07-24T09:02:45Z",
|
||||
},
|
||||
{
|
||||
nama: "Rafi",
|
||||
role: 2,
|
||||
chat: "Oke, aku coba edit malam ini ya. Nanti aku share ulang versinya.",
|
||||
time: "2025-07-24T09:03:10Z",
|
||||
},
|
||||
{
|
||||
nama: "Dina",
|
||||
role: 1,
|
||||
chat: "Siap, makasih ya. Jangan begadang!",
|
||||
time: "2025-07-24T09:03:30Z",
|
||||
},
|
||||
];
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
paddingVertical: 10,
|
||||
paddingHorizontal: 12,
|
||||
},
|
||||
messageRow: {
|
||||
flexDirection: "row",
|
||||
marginBottom: 12,
|
||||
},
|
||||
rightAlign: {
|
||||
justifyContent: "flex-end",
|
||||
},
|
||||
leftAlign: {
|
||||
justifyContent: "flex-start",
|
||||
},
|
||||
bubble: {
|
||||
maxWidth: "75%",
|
||||
padding: 10,
|
||||
borderRadius: 12,
|
||||
},
|
||||
bubbleRight: {
|
||||
backgroundColor: "#DCF8C6", // hijau muda
|
||||
borderTopRightRadius: 0,
|
||||
},
|
||||
bubbleLeft: {
|
||||
backgroundColor: "#F0F0F0", // abu-abu terang
|
||||
borderTopLeftRadius: 0,
|
||||
},
|
||||
sender: {
|
||||
fontSize: 12,
|
||||
fontWeight: "bold",
|
||||
marginBottom: 2,
|
||||
color: "#555",
|
||||
},
|
||||
message: {
|
||||
fontSize: 15,
|
||||
color: "#000",
|
||||
},
|
||||
time: {
|
||||
fontSize: 10,
|
||||
color: "#888",
|
||||
textAlign: "right",
|
||||
marginTop: 4,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
import {
|
||||
AlertDefaultSystem,
|
||||
ButtonCustom,
|
||||
TextAreaCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
import { apiCollaborationCreatePartisipasi } from "@/service/api-client/api-collaboration";
|
||||
import { router, useLocalSearchParams } from "expo-router";
|
||||
import { useState } from "react";
|
||||
import Toast from "react-native-toast-message";
|
||||
|
||||
export default function CollaborationCreatePartisipans() {
|
||||
const { user } = useAuth();
|
||||
const { id } = useLocalSearchParams();
|
||||
const [description, setDescription] = useState("");
|
||||
const [isLoading, setLoading] = useState(false);
|
||||
|
||||
const handlerSubmitParticipans = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const response = await apiCollaborationCreatePartisipasi({
|
||||
id: id as string,
|
||||
data: {
|
||||
authorId: user?.id,
|
||||
description,
|
||||
},
|
||||
});
|
||||
|
||||
if (response.success) {
|
||||
Toast.show({
|
||||
type: "success",
|
||||
text1: "Data berhasil disimpan",
|
||||
});
|
||||
router.replace(`/collaboration/${id}/list-of-participants`);
|
||||
} else {
|
||||
Toast.show({
|
||||
type: "error",
|
||||
text1: "Gagal menyimpan data",
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<ViewWrapper>
|
||||
<TextAreaCustom
|
||||
// label="Deskripsi"
|
||||
placeholder="Masukan deskripsi diri anda .."
|
||||
value={description}
|
||||
onChangeText={setDescription}
|
||||
required
|
||||
showCount
|
||||
maxLength={1000}
|
||||
/>
|
||||
|
||||
<ButtonCustom
|
||||
disabled={description.length === 0}
|
||||
isLoading={isLoading}
|
||||
onPress={() => {
|
||||
AlertDefaultSystem({
|
||||
title: "Simpan data deskripsi",
|
||||
message: "Apakah anda sudah yakin ingin menyimpan data ini ?",
|
||||
textLeft: "Batal",
|
||||
textRight: "Simpan",
|
||||
onPressRight: () => {
|
||||
handlerSubmitParticipans();
|
||||
},
|
||||
});
|
||||
}}
|
||||
>
|
||||
Simpan
|
||||
</ButtonCustom>
|
||||
</ViewWrapper>
|
||||
);
|
||||
}
|
||||
@@ -1,26 +1,54 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
AvatarUsernameAndOtherComponent,
|
||||
BaseBox,
|
||||
BackButton,
|
||||
DotButton,
|
||||
DrawerCustom,
|
||||
StackCustom,
|
||||
TextCustom,
|
||||
ViewWrapper,
|
||||
MenuDrawerDynamicGrid,
|
||||
ViewWrapper
|
||||
} from "@/components";
|
||||
import { ICON_SIZE_SMALL } from "@/constants/constans-value";
|
||||
import Collaboration_BoxDetailSection from "@/screens/Collaboration/BoxDetailSection";
|
||||
import { MaterialIcons } from "@expo/vector-icons";
|
||||
import { useLocalSearchParams } from "expo-router";
|
||||
import { useState } from "react";
|
||||
import { apiCollaborationGetOne } from "@/service/api-client/api-collaboration";
|
||||
import { Ionicons } from "@expo/vector-icons";
|
||||
import { router, Stack, useFocusEffect, useLocalSearchParams } from "expo-router";
|
||||
import { useCallback, useState } from "react";
|
||||
|
||||
export default function CollaborationDetailParticipant() {
|
||||
const { id } = useLocalSearchParams();
|
||||
const [openDrawerParticipant, setOpenDrawerParticipant] = useState(false);
|
||||
const [data, setData] = useState<any>();
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
onLoadData();
|
||||
}, [id])
|
||||
);
|
||||
|
||||
const onLoadData = async () => {
|
||||
try {
|
||||
const response = await apiCollaborationGetOne({ id: id as string });
|
||||
if (response.success) {
|
||||
setData(response.data);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Stack.Screen
|
||||
options={{
|
||||
title: "Detail Proyek",
|
||||
headerLeft: () => <BackButton />,
|
||||
headerRight: () => (
|
||||
<DotButton onPress={() => setOpenDrawerParticipant(true)} />
|
||||
),
|
||||
}}
|
||||
/>
|
||||
|
||||
<ViewWrapper>
|
||||
<Collaboration_BoxDetailSection id={id as string} />
|
||||
<BaseBox style={{ height: 500 }}>
|
||||
<Collaboration_BoxDetailSection data={data} />
|
||||
{/* <BaseBox style={{ height: 500 }}>
|
||||
<TextCustom align="center" bold size="large">
|
||||
Partisipan
|
||||
</TextCustom>
|
||||
@@ -39,13 +67,33 @@ export default function CollaborationDetailParticipant() {
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</BaseBox>
|
||||
</BaseBox> */}
|
||||
</ViewWrapper>
|
||||
|
||||
<DrawerCustom
|
||||
isVisible={openDrawerParticipant}
|
||||
closeDrawer={() => setOpenDrawerParticipant(false)}
|
||||
height={"auto"}
|
||||
>
|
||||
<MenuDrawerDynamicGrid
|
||||
data={[
|
||||
{
|
||||
icon: <Ionicons name="people" size={24} color="white" />,
|
||||
label: "Daftar Partisipan",
|
||||
path: `/collaboration/${id}/list-of-participants`,
|
||||
},
|
||||
]}
|
||||
onPressItem={(item) => {
|
||||
router.push(item.path as any);
|
||||
setOpenDrawerParticipant(false);
|
||||
}}
|
||||
/>
|
||||
</DrawerCustom>
|
||||
|
||||
{/* <DrawerCustom
|
||||
isVisible={openDrawerParticipant}
|
||||
closeDrawer={() => setOpenDrawerParticipant(false)}
|
||||
height={"auto"}
|
||||
>
|
||||
<StackCustom>
|
||||
<TextCustom bold>Deskripsi Diri</TextCustom>
|
||||
@@ -56,7 +104,7 @@ export default function CollaborationDetailParticipant() {
|
||||
Temporibus iusto soluta necessitatibus.
|
||||
</TextCustom>
|
||||
</StackCustom>
|
||||
</DrawerCustom>
|
||||
</DrawerCustom> */}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,30 +1,65 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
AlertDefaultSystem,
|
||||
BackButton,
|
||||
ButtonCustom,
|
||||
DotButton,
|
||||
DrawerCustom,
|
||||
LoaderCustom,
|
||||
MenuDrawerDynamicGrid,
|
||||
Spacing,
|
||||
StackCustom,
|
||||
TextCustom,
|
||||
ViewWrapper
|
||||
} from "@/components";
|
||||
import { IconEdit } from "@/components/_Icon";
|
||||
import Collaboration_BoxDetailSection from "@/screens/Collaboration/BoxDetailSection";
|
||||
import Collaboration_MainParticipanSelectedSection from "@/screens/Collaboration/ProjectMainSelectedSection";
|
||||
import { router, Stack, useLocalSearchParams } from "expo-router";
|
||||
import { useState } from "react";
|
||||
import {
|
||||
apiCollaborationGetOne
|
||||
} from "@/service/api-client/api-collaboration";
|
||||
import { MaterialIcons } from "@expo/vector-icons";
|
||||
import {
|
||||
router,
|
||||
Stack,
|
||||
useFocusEffect,
|
||||
useLocalSearchParams,
|
||||
} from "expo-router";
|
||||
import { useCallback, useState } from "react";
|
||||
|
||||
export default function CollaborationDetailProjectMain() {
|
||||
const { id } = useLocalSearchParams();
|
||||
const [openDrawer, setOpenDrawer] = useState(false);
|
||||
const [openDrawerParticipant, setOpenDrawerParticipant] = useState(false);
|
||||
const [selected, setSelected] = useState<(string | number)[]>([]);
|
||||
const [data, setData] = useState<any>();
|
||||
const [loadingGetData, setLoadingGetData] = useState(false);
|
||||
|
||||
const handleEdit = () => {
|
||||
console.log("Edit collaboration");
|
||||
router.push("/(application)/(user)/collaboration/(id)/edit");
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
handlerLoadData();
|
||||
}, [id])
|
||||
);
|
||||
|
||||
const handlerLoadData = async () => {
|
||||
try {
|
||||
setLoadingGetData(true);
|
||||
await onLoadData();
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
} finally {
|
||||
setLoadingGetData(false);
|
||||
}
|
||||
};
|
||||
|
||||
const onLoadData = async () => {
|
||||
try {
|
||||
const response = await apiCollaborationGetOne({ id: id as string });
|
||||
if (response.success) {
|
||||
setData(response.data);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSubmit = (item: any) => {
|
||||
console.log("item :", item);
|
||||
router.push(item.path);
|
||||
setOpenDrawer(false);
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -37,34 +72,21 @@ export default function CollaborationDetailProjectMain() {
|
||||
}}
|
||||
/>
|
||||
<ViewWrapper>
|
||||
<Collaboration_BoxDetailSection id={id as string} />
|
||||
<Collaboration_MainParticipanSelectedSection
|
||||
selected={selected}
|
||||
setSelected={setSelected}
|
||||
setOpenDrawerParticipant={setOpenDrawerParticipant}
|
||||
/>
|
||||
{loadingGetData ? (
|
||||
<LoaderCustom />
|
||||
) : (
|
||||
<>
|
||||
<Collaboration_BoxDetailSection data={data} />
|
||||
{/* <Collaboration_MainParticipanSelectedSection
|
||||
selected={selected}
|
||||
setSelected={setSelected}
|
||||
setOpenDrawerParticipant={setOpenDrawerParticipant}
|
||||
listData={listData as any}
|
||||
/> */}
|
||||
|
||||
<ButtonCustom
|
||||
onPress={() => {
|
||||
AlertDefaultSystem({
|
||||
title: "Buat Grup",
|
||||
message:
|
||||
"Apakah anda yakin ingin membuat grup untuk proyek ini ?",
|
||||
textLeft: "Tidak",
|
||||
textRight: "Ya",
|
||||
onPressLeft: () => {},
|
||||
onPressRight: () => {
|
||||
router.navigate(
|
||||
"/(application)/(user)/collaboration/(tabs)/group"
|
||||
);
|
||||
console.log("selected :", selected);
|
||||
},
|
||||
});
|
||||
}}
|
||||
>
|
||||
Buat Grup
|
||||
</ButtonCustom>
|
||||
<Spacing />
|
||||
<Spacing />
|
||||
</>
|
||||
)}
|
||||
</ViewWrapper>
|
||||
|
||||
<DrawerCustom
|
||||
@@ -76,31 +98,20 @@ export default function CollaborationDetailProjectMain() {
|
||||
data={[
|
||||
{
|
||||
label: "Edit",
|
||||
path: "/(application)/(user)/collaboration/(tabs)/group",
|
||||
path: `/(application)/(user)/collaboration/${id}/edit`,
|
||||
icon: <IconEdit />,
|
||||
},
|
||||
{
|
||||
label: "Pilih Partisipan",
|
||||
path: `/(application)/(user)/collaboration/${id}/select-of-participants`,
|
||||
icon: <MaterialIcons name="checklist" size={24} color="white" />,
|
||||
},
|
||||
]}
|
||||
onPressItem={(item) => {
|
||||
handleEdit();
|
||||
onPressItem={(item: any) => {
|
||||
handleSubmit(item);
|
||||
}}
|
||||
/>
|
||||
</DrawerCustom>
|
||||
|
||||
<DrawerCustom
|
||||
isVisible={openDrawerParticipant}
|
||||
closeDrawer={() => setOpenDrawerParticipant(false)}
|
||||
height={"auto"}
|
||||
>
|
||||
<StackCustom>
|
||||
<TextCustom bold>Deskripsi Diri</TextCustom>
|
||||
<TextCustom>
|
||||
Lorem ipsum dolor sit, amet consectetur adipisicing elit. Commodi,
|
||||
itaque adipisci. Voluptas, sed quod! Ad facere labore voluptates,
|
||||
neque quidem aut reprehenderit ducimus mollitia quisquam temporibus!
|
||||
Temporibus iusto soluta necessitatibus.
|
||||
</TextCustom>
|
||||
</StackCustom>
|
||||
</DrawerCustom>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,53 +1,170 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
ButtonCustom,
|
||||
LoaderCustom,
|
||||
SelectCustom,
|
||||
StackCustom,
|
||||
TextAreaCustom,
|
||||
TextInputCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import { router } from "expo-router";
|
||||
import {
|
||||
apiCollaborationEditData,
|
||||
apiCollaborationGetOne,
|
||||
} from "@/service/api-client/api-collaboration";
|
||||
import { apiMasterCollaborationType } from "@/service/api-client/api-master";
|
||||
import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
|
||||
import { useCallback, useState } from "react";
|
||||
import Toast from "react-native-toast-message";
|
||||
|
||||
export default function CollaborationEdit() {
|
||||
const { id } = useLocalSearchParams();
|
||||
const [data, setData] = useState<any>();
|
||||
const [listMaster, setListMaster] = useState<any[]>([]);
|
||||
const [loadingData, setLoadingData] = useState(false);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
const fetchData = async () => {
|
||||
try {
|
||||
setLoadingData(true);
|
||||
await onLoadData();
|
||||
await onLoadMaster();
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
} finally {
|
||||
setLoadingData(false);
|
||||
}
|
||||
};
|
||||
fetchData();
|
||||
}, [id])
|
||||
);
|
||||
|
||||
const onLoadData = async () => {
|
||||
try {
|
||||
const response = await apiCollaborationGetOne({ id: id as string });
|
||||
|
||||
if (response.success) {
|
||||
setData(response.data);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
}
|
||||
};
|
||||
|
||||
async function onLoadMaster() {
|
||||
try {
|
||||
const response = await apiMasterCollaborationType();
|
||||
setListMaster(response.data);
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
}
|
||||
}
|
||||
|
||||
const handlerSubmitUpdate = async () => {
|
||||
if (
|
||||
!data?.title ||
|
||||
!data?.lokasi ||
|
||||
!data?.projectCollaborationMaster_IndustriId ||
|
||||
!data?.purpose ||
|
||||
!data?.benefit
|
||||
) {
|
||||
Toast.show({
|
||||
type: "error",
|
||||
text1: "Gagal",
|
||||
text2: "Harap isi semua data",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
setIsLoading(true);
|
||||
const response = await apiCollaborationEditData({
|
||||
id: id as string,
|
||||
data: data,
|
||||
});
|
||||
|
||||
if (response.success) {
|
||||
Toast.show({
|
||||
type: "success",
|
||||
text1: response.message,
|
||||
});
|
||||
router.back();
|
||||
} else {
|
||||
Toast.show({
|
||||
type: "error",
|
||||
text1: response.message,
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<ViewWrapper>
|
||||
<StackCustom gap={"xs"}>
|
||||
<TextInputCustom label="Judul" placeholder="Masukan judul" required />
|
||||
<TextInputCustom label="Lokasi" placeholder="Masukan lokasi" required />
|
||||
<SelectCustom
|
||||
label="Pilih Industri"
|
||||
data={[
|
||||
{ label: "Industri 1", value: "industri-1" },
|
||||
{ label: "Industri 2", value: "industri-2" },
|
||||
{ label: "Industri 3", value: "industri-3" },
|
||||
]}
|
||||
onChange={(value) => console.log(value)}
|
||||
/>
|
||||
{loadingData ? (
|
||||
<LoaderCustom />
|
||||
) : (
|
||||
<StackCustom gap={"xs"}>
|
||||
<TextInputCustom
|
||||
label="Judul"
|
||||
placeholder="Masukan judul"
|
||||
required
|
||||
value={data?.title}
|
||||
onChangeText={(value) => setData({ ...data, title: value })}
|
||||
/>
|
||||
<TextInputCustom
|
||||
label="Lokasi"
|
||||
placeholder="Masukan lokasi"
|
||||
required
|
||||
value={data?.lokasi}
|
||||
onChangeText={(value) => setData({ ...data, lokasi: value })}
|
||||
/>
|
||||
<SelectCustom
|
||||
label="Pilih Industri"
|
||||
data={listMaster?.map((item: any) => ({
|
||||
label: item.name,
|
||||
value: item.id,
|
||||
}))}
|
||||
value={data?.projectCollaborationMaster_IndustriId}
|
||||
onChange={(value) =>
|
||||
setData({ ...data, projectCollaborationMaster_IndustriId: value })
|
||||
}
|
||||
/>
|
||||
|
||||
<TextAreaCustom
|
||||
required
|
||||
label="Tujuan Proyek"
|
||||
placeholder="Masukan tujuan proyek"
|
||||
showCount
|
||||
maxLength={1000}
|
||||
/>
|
||||
<TextAreaCustom
|
||||
required
|
||||
label="Tujuan Proyek"
|
||||
placeholder="Masukan tujuan proyek"
|
||||
showCount
|
||||
maxLength={1000}
|
||||
value={data?.purpose}
|
||||
onChangeText={(value) => setData({ ...data, purpose: value })}
|
||||
/>
|
||||
|
||||
<TextAreaCustom
|
||||
required
|
||||
label="Keuntungan Proyek"
|
||||
placeholder="Masukan keuntungan proyek"
|
||||
showCount
|
||||
maxLength={1000}
|
||||
/>
|
||||
<TextAreaCustom
|
||||
required
|
||||
label="Keuntungan Proyek"
|
||||
placeholder="Masukan keuntungan proyek"
|
||||
showCount
|
||||
maxLength={1000}
|
||||
value={data?.benefit}
|
||||
onChangeText={(value) => setData({ ...data, benefit: value })}
|
||||
/>
|
||||
|
||||
<ButtonCustom
|
||||
title="Update"
|
||||
onPress={() => {
|
||||
console.log("Update proyek");
|
||||
router.back();
|
||||
}}
|
||||
/>
|
||||
</StackCustom>
|
||||
<ButtonCustom
|
||||
isLoading={isLoading}
|
||||
title="Update"
|
||||
onPress={() => {
|
||||
handlerSubmitUpdate();
|
||||
}}
|
||||
/>
|
||||
</StackCustom>
|
||||
)}
|
||||
</ViewWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,22 +1,75 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
AlertDefaultSystem,
|
||||
BackButton,
|
||||
ButtonCustom,
|
||||
DotButton,
|
||||
DrawerCustom,
|
||||
InformationBox,
|
||||
LoaderCustom,
|
||||
MenuDrawerDynamicGrid,
|
||||
TextAreaCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
import Collaboration_BoxDetailSection from "@/screens/Collaboration/BoxDetailSection";
|
||||
import {
|
||||
apiCollaborationGetOne,
|
||||
apiCollaborationGetParticipants,
|
||||
} from "@/service/api-client/api-collaboration";
|
||||
import { Ionicons } from "@expo/vector-icons";
|
||||
import { router, Stack, useLocalSearchParams } from "expo-router";
|
||||
import { useState } from "react";
|
||||
import {
|
||||
router,
|
||||
Stack,
|
||||
useFocusEffect,
|
||||
useLocalSearchParams,
|
||||
} from "expo-router";
|
||||
import { useCallback, useState } from "react";
|
||||
|
||||
export default function CollaborationDetail() {
|
||||
const { user } = useAuth();
|
||||
const { id } = useLocalSearchParams();
|
||||
const [openDrawerPartisipasi, setOpenDrawerPartisipasi] = useState(false);
|
||||
const [data, setData] = useState<any>();
|
||||
const [openDrawerMenu, setOpenDrawerMenu] = useState(false);
|
||||
const [isParticipant, setIsParticipant] = useState(false);
|
||||
const [loadingIsParticipant, setLoadingIsParticipant] = useState(false);
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
onLoadData();
|
||||
onLoadParticipants();
|
||||
}, [id])
|
||||
);
|
||||
|
||||
const onLoadData = async () => {
|
||||
try {
|
||||
const response = await apiCollaborationGetOne({ id: id as string });
|
||||
|
||||
if (response.success) {
|
||||
setData(response.data);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
}
|
||||
};
|
||||
|
||||
const onLoadParticipants = async () => {
|
||||
try {
|
||||
setLoadingIsParticipant(true);
|
||||
const response = await apiCollaborationGetParticipants({
|
||||
category: "check-participant",
|
||||
id: id as string,
|
||||
authorId: user?.id,
|
||||
});
|
||||
|
||||
if (response.success) {
|
||||
setIsParticipant(response.data);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
} finally {
|
||||
setLoadingIsParticipant(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Stack.Screen
|
||||
@@ -29,15 +82,35 @@ export default function CollaborationDetail() {
|
||||
}}
|
||||
/>
|
||||
<ViewWrapper>
|
||||
<Collaboration_BoxDetailSection id={id as string} />
|
||||
|
||||
<ButtonCustom onPress={() => setOpenDrawerPartisipasi(true)}>
|
||||
Partisipasi
|
||||
</ButtonCustom>
|
||||
{!data && !isParticipant ? (
|
||||
<LoaderCustom />
|
||||
) : (
|
||||
<>
|
||||
{user?.id === data?.Author?.id && (
|
||||
<InformationBox
|
||||
text={
|
||||
"Tombol partisipasi hanya muncul pada proyek milik orang lain"
|
||||
}
|
||||
/>
|
||||
)}
|
||||
<Collaboration_BoxDetailSection data={data} />
|
||||
{user?.id !== data?.Author?.id && (
|
||||
<ButtonCustom
|
||||
disabled={isParticipant || loadingIsParticipant}
|
||||
onPress={() => {
|
||||
router.push(`/collaboration/${id}/create-pacticipants`);
|
||||
// setOpenDrawerPartisipasi(true);
|
||||
}}
|
||||
>
|
||||
{isParticipant ? "Anda telah berpartisipasi" : "Partisipasi"}
|
||||
</ButtonCustom>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</ViewWrapper>
|
||||
|
||||
{/* Drawer Partisipasi */}
|
||||
<DrawerCustom
|
||||
{/* <DrawerCustom
|
||||
isVisible={openDrawerPartisipasi}
|
||||
closeDrawer={() => setOpenDrawerPartisipasi(false)}
|
||||
height={300}
|
||||
@@ -48,6 +121,8 @@ export default function CollaborationDetail() {
|
||||
required
|
||||
showCount
|
||||
maxLength={500}
|
||||
value={description}
|
||||
onChangeText={setDescription}
|
||||
/>
|
||||
|
||||
<ButtonCustom
|
||||
@@ -58,19 +133,21 @@ export default function CollaborationDetail() {
|
||||
message: "Apakah anda sudah yakin ingin menyimpan data ini ?",
|
||||
textLeft: "Batal",
|
||||
textRight: "Simpan",
|
||||
onPressRight: () => router.replace(`/collaboration/(tabs)/group`),
|
||||
onPressRight: () => {
|
||||
handlerSubmitParticipans();
|
||||
},
|
||||
});
|
||||
}}
|
||||
>
|
||||
Simpan
|
||||
</ButtonCustom>
|
||||
</DrawerCustom>
|
||||
</DrawerCustom> */}
|
||||
|
||||
{/* Drawer Menu */}
|
||||
<DrawerCustom
|
||||
isVisible={openDrawerMenu}
|
||||
closeDrawer={() => setOpenDrawerMenu(false)}
|
||||
height={250}
|
||||
height={"auto"}
|
||||
>
|
||||
<MenuDrawerDynamicGrid
|
||||
data={[
|
||||
|
||||
@@ -1,38 +1,79 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
AvatarUsernameAndOtherComponent,
|
||||
BaseBox,
|
||||
DrawerCustom,
|
||||
Spacing,
|
||||
StackCustom,
|
||||
TextCustom,
|
||||
ViewWrapper
|
||||
AvatarUsernameAndOtherComponent,
|
||||
BaseBox,
|
||||
DrawerCustom,
|
||||
LoaderCustom,
|
||||
Spacing,
|
||||
StackCustom,
|
||||
TextCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import { apiCollaborationGetParticipants } from "@/service/api-client/api-collaboration";
|
||||
import { Feather } from "@expo/vector-icons";
|
||||
import { useLocalSearchParams } from "expo-router";
|
||||
import { useState } from "react";
|
||||
import _ from "lodash";
|
||||
import { useEffect, useState } from "react";
|
||||
import { ScrollView } from "react-native";
|
||||
|
||||
export default function CollaborationListOfParticipants() {
|
||||
const { id } = useLocalSearchParams();
|
||||
const [listData, setListData] = useState<any[]>();
|
||||
const [loadingGetData, setLoadingGetData] = useState(false);
|
||||
const [description, setDescription] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
onLoadData();
|
||||
}, [id]);
|
||||
|
||||
const onLoadData = async () => {
|
||||
try {
|
||||
setLoadingGetData(true);
|
||||
const response = await apiCollaborationGetParticipants({
|
||||
category: "list",
|
||||
id: id as string,
|
||||
});
|
||||
|
||||
if (response.success) {
|
||||
setListData(response.data);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
} finally {
|
||||
setLoadingGetData(false);
|
||||
}
|
||||
};
|
||||
|
||||
const [openDrawer, setOpenDrawer] = useState(false);
|
||||
return (
|
||||
<>
|
||||
<ViewWrapper>
|
||||
{Array.from({ length: 10 }).map((_, index) => (
|
||||
<BaseBox key={index} paddingBlock={5}>
|
||||
<AvatarUsernameAndOtherComponent
|
||||
avatarHref={`/profile/${id}`}
|
||||
rightComponent={
|
||||
<Feather
|
||||
name="chevron-right"
|
||||
size={24}
|
||||
color="white"
|
||||
onPress={() => setOpenDrawer(true)}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</BaseBox>
|
||||
))}
|
||||
{loadingGetData ? (
|
||||
<LoaderCustom />
|
||||
) : _.isEmpty(listData) ? (
|
||||
<TextCustom align="center">Tidak ada partisipan</TextCustom>
|
||||
) : (
|
||||
listData?.map((item: any, index: number) => (
|
||||
<BaseBox key={index} paddingBlock={5}>
|
||||
<AvatarUsernameAndOtherComponent
|
||||
avatar={item?.User?.Profile?.imageId}
|
||||
avatarHref={`/profile/${item?.User?.Profile?.id}`}
|
||||
name={item?.User?.username}
|
||||
rightComponent={
|
||||
<Feather
|
||||
name="chevron-right"
|
||||
size={24}
|
||||
color="white"
|
||||
onPress={() => {
|
||||
setDescription(item?.deskripsi_diri);
|
||||
setOpenDrawer(true);
|
||||
}}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</BaseBox>
|
||||
))
|
||||
)}
|
||||
</ViewWrapper>
|
||||
|
||||
{/* Drawer */}
|
||||
@@ -44,34 +85,7 @@ export default function CollaborationListOfParticipants() {
|
||||
<TextCustom bold>Deskripsi diri</TextCustom>
|
||||
<BaseBox>
|
||||
<ScrollView style={{ height: "80%" }}>
|
||||
<TextCustom>
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
|
||||
eiusmod tempor incididunt ut labore et dolore magna aliqua.
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
|
||||
eiusmod tempor incididunt ut labore et dolore magna aliqua.Lorem
|
||||
ipsum dolor sit amet, consectetur adipiscing elit, sed do
|
||||
eiusmod tempor incididunt ut labore et dolore magna aliqua.Lorem
|
||||
ipsum dolor sit amet, consectetur adipiscing elit, sed do
|
||||
eiusmod tempor incididunt ut labore et dolore magna aliqua.Lorem
|
||||
ipsum dolor sit amet, consectetur adipiscing elit, sed do
|
||||
eiusmod tempor incididunt ut iqua.Lorem ipsum dolor sit amet,
|
||||
consectetur adipiscing elit, sed do eiusmod tempor incididunt ut
|
||||
labore et dolore magna aliqua.Lorem ipsum dolor sit amet,
|
||||
consectetur adipiscing elit, sed do eiusmod tempor incididunt ut
|
||||
labore et dolore magna aliqua.Lorem ipsum dolor sit amet,
|
||||
consectetur adipiscing elit, sed do eiusmod tempor incididunt ut
|
||||
labore et dolore magna aliqua.Lorem ipsum dolor sit amet,
|
||||
consectetur adipiscing elit, sed do eiusmod tempor incididunt ut
|
||||
labore et dolore magna aliqua.Lorem ipsum dolor sit amet,
|
||||
consectetur adipiscing elit, sed do eiusmod tempor incididunt ut
|
||||
labore et dolore magna aliqua.Lorem ipsum dolor sit amet,
|
||||
consectetur adipiscing elit, sed do eiusmod tempor incididunt ut
|
||||
labore et dolore magna aliqua.Lorem ipsum dolor sit amet,
|
||||
consectetur adipiscing elit, sed do eiusmod tempor incididunt ut
|
||||
labore et dolore magna aliqua.Lorem ipsum dolor sit amet,
|
||||
consectetur adipiscing elit, sed do eiusmod tempor incididunt ut
|
||||
labore et dolore magna aliqua.
|
||||
</TextCustom>
|
||||
<TextCustom>{description}</TextCustom>
|
||||
</ScrollView>
|
||||
</BaseBox>
|
||||
<Spacing />
|
||||
|
||||
@@ -0,0 +1,251 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
AlertDefaultSystem,
|
||||
AvatarComp,
|
||||
BaseBox,
|
||||
BoxButtonOnFooter,
|
||||
ButtonCustom,
|
||||
CheckboxCustom,
|
||||
CheckboxGroup,
|
||||
DrawerCustom,
|
||||
Grid,
|
||||
LoaderCustom,
|
||||
Spacing,
|
||||
StackCustom,
|
||||
TextAreaCustom,
|
||||
TextCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import ModalCustom from "@/components/Modal/ModalCustom";
|
||||
import { ICON_SIZE_SMALL } from "@/constants/constans-value";
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
import {
|
||||
apiCollaborationCreateGroup,
|
||||
apiCollaborationGetParticipants,
|
||||
} from "@/service/api-client/api-collaboration";
|
||||
import { MaterialIcons } from "@expo/vector-icons";
|
||||
import { router, useLocalSearchParams } from "expo-router";
|
||||
import _ from "lodash";
|
||||
import { useEffect, useState } from "react";
|
||||
import { ScrollView, View } from "react-native";
|
||||
import Toast from "react-native-toast-message";
|
||||
|
||||
export default function CollaborationSelectOfParticipants() {
|
||||
const { user } = useAuth();
|
||||
const { id } = useLocalSearchParams();
|
||||
const [listData, setListData] = useState<any[]>();
|
||||
const [loadingGetData, setLoadingGetData] = useState(false);
|
||||
const [description, setDescription] = useState("");
|
||||
const [selected, setSelected] = useState<(string | number)[]>([]);
|
||||
const [openDrawer, setOpenDrawer] = useState(false);
|
||||
const [nameGroup, setNameGroup] = useState("");
|
||||
const [openModal, setOpenModal] = useState(false);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
onLoadData();
|
||||
}, [id]);
|
||||
|
||||
const onLoadData = async () => {
|
||||
try {
|
||||
setLoadingGetData(true);
|
||||
const response = await apiCollaborationGetParticipants({
|
||||
category: "list",
|
||||
id: id as string,
|
||||
});
|
||||
// console.log("response :", JSON.stringify(response.data, null, 2));
|
||||
|
||||
if (response.success) {
|
||||
setListData(response.data);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
} finally {
|
||||
setLoadingGetData(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handlerCreateGroup = async () => {
|
||||
if (_.isEmpty(nameGroup)) {
|
||||
Toast.show({
|
||||
type: "error",
|
||||
text1: "Nama grup tidak boleh kosong",
|
||||
});
|
||||
return;
|
||||
}
|
||||
try {
|
||||
setIsLoading(true);
|
||||
const response = await apiCollaborationCreateGroup({
|
||||
id: id as string,
|
||||
data: {
|
||||
authorId: user?.id,
|
||||
nameGroup: nameGroup,
|
||||
listSelect: selected,
|
||||
},
|
||||
});
|
||||
|
||||
if (response.success) {
|
||||
Toast.show({
|
||||
type: "success",
|
||||
text1: "Grup berhasil dibuat",
|
||||
});
|
||||
router.push("/(application)/(user)/collaboration/(tabs)/group");
|
||||
} else {
|
||||
Toast.show({
|
||||
type: "error",
|
||||
text1: "Gagal membuat grup",
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handlerSubmit = () => {
|
||||
return (
|
||||
<>
|
||||
<BoxButtonOnFooter>
|
||||
<ButtonCustom
|
||||
disabled={_.isEmpty(selected)}
|
||||
onPress={() => {
|
||||
setOpenModal(true);
|
||||
setNameGroup("");
|
||||
}}
|
||||
>
|
||||
Buat Grup
|
||||
</ButtonCustom>
|
||||
</BoxButtonOnFooter>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<ViewWrapper
|
||||
footerComponent={_.isEmpty(listData) ? null : handlerSubmit()}
|
||||
>
|
||||
{loadingGetData ? (
|
||||
<LoaderCustom />
|
||||
) : _.isEmpty(listData) ? (
|
||||
<TextCustom align="center">Tidak ada partisipan</TextCustom>
|
||||
) : (
|
||||
<StackCustom>
|
||||
<TextCustom size="default" color="red" bold>
|
||||
*{" "}
|
||||
<TextCustom size="small" semiBold>
|
||||
Pilih user yang akan menjadi tim proyek anda
|
||||
</TextCustom>
|
||||
</TextCustom>
|
||||
|
||||
<CheckboxGroup
|
||||
value={selected}
|
||||
onChange={(val: any) => {
|
||||
console.log("val :", val);
|
||||
setSelected(val);
|
||||
}}
|
||||
>
|
||||
{listData?.map((item: any, index: any) => (
|
||||
<View key={index}>
|
||||
<Grid key={index}>
|
||||
<Grid.Col
|
||||
span={2}
|
||||
style={{ alignItems: "center", justifyContent: "center" }}
|
||||
>
|
||||
<CheckboxCustom valueKey={item?.User?.id} />
|
||||
</Grid.Col>
|
||||
<Grid.Col span={2} style={{ alignItems: "center" }}>
|
||||
<AvatarComp
|
||||
size="base"
|
||||
fileId={item?.User?.Profile?.imageId}
|
||||
/>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={6} style={{ justifyContent: "center" }}>
|
||||
<TextCustom bold truncate>
|
||||
{item?.User?.username}
|
||||
</TextCustom>
|
||||
</Grid.Col>
|
||||
<Grid.Col
|
||||
span={2}
|
||||
style={{ alignItems: "center", justifyContent: "center" }}
|
||||
>
|
||||
<MaterialIcons
|
||||
name="notes"
|
||||
size={ICON_SIZE_SMALL}
|
||||
color="white"
|
||||
onPress={() => {
|
||||
setOpenDrawer(true);
|
||||
setDescription(item?.deskripsi_diri);
|
||||
}}
|
||||
/>
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
</View>
|
||||
))}
|
||||
</CheckboxGroup>
|
||||
</StackCustom>
|
||||
)}
|
||||
</ViewWrapper>
|
||||
|
||||
<ModalCustom isVisible={openModal}>
|
||||
<StackCustom gap={0}>
|
||||
<TextAreaCustom
|
||||
placeholder="Nama Grup"
|
||||
value={nameGroup}
|
||||
onChangeText={(val) => setNameGroup(val)}
|
||||
/>
|
||||
<Grid>
|
||||
<Grid.Col span={6} style={{ paddingRight: 10 }}>
|
||||
<ButtonCustom
|
||||
backgroundColor="gray"
|
||||
onPress={() => {
|
||||
setOpenModal(false);
|
||||
}}
|
||||
>
|
||||
Batal
|
||||
</ButtonCustom>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={6} style={{ paddingLeft: 10 }}>
|
||||
<ButtonCustom
|
||||
isLoading={isLoading}
|
||||
disabled={_.isEmpty(nameGroup)}
|
||||
onPress={() => {
|
||||
AlertDefaultSystem({
|
||||
title: "Buat Grup",
|
||||
message:
|
||||
"Apakah anda yakin ingin membuat grup untuk proyek ini ?",
|
||||
textLeft: "Tidak",
|
||||
textRight: "Ya",
|
||||
onPressLeft: () => {},
|
||||
onPressRight: () => {
|
||||
handlerCreateGroup();
|
||||
},
|
||||
});
|
||||
}}
|
||||
>
|
||||
Simpan
|
||||
</ButtonCustom>
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
</StackCustom>
|
||||
</ModalCustom>
|
||||
|
||||
{/* Drawer */}
|
||||
<DrawerCustom
|
||||
isVisible={openDrawer}
|
||||
closeDrawer={() => setOpenDrawer(false)}
|
||||
>
|
||||
<StackCustom>
|
||||
<TextCustom bold>Deskripsi diri</TextCustom>
|
||||
<BaseBox>
|
||||
<ScrollView style={{ height: "80%" }}>
|
||||
<TextCustom>{description}</TextCustom>
|
||||
</ScrollView>
|
||||
</BaseBox>
|
||||
<Spacing />
|
||||
</StackCustom>
|
||||
</DrawerCustom>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,53 +1,174 @@
|
||||
import {
|
||||
ButtonCustom,
|
||||
SelectCustom,
|
||||
StackCustom,
|
||||
TextAreaCustom,
|
||||
TextInputCustom,
|
||||
ViewWrapper
|
||||
ButtonCustom,
|
||||
LoaderCustom,
|
||||
SelectCustom,
|
||||
StackCustom,
|
||||
TextAreaCustom,
|
||||
TextInputCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
import { apiCollaborationCreate } from "@/service/api-client/api-collaboration";
|
||||
import { apiMasterCollaborationType } from "@/service/api-client/api-master";
|
||||
import { router } from "expo-router";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import Toast from "react-native-toast-message";
|
||||
|
||||
interface CollaborationCreateProps {
|
||||
title?: string;
|
||||
lokasi?: string;
|
||||
purpose?: string;
|
||||
benefit?: string;
|
||||
projectCollaborationMaster_IndustriId?: string;
|
||||
userId?: string;
|
||||
}
|
||||
|
||||
export default function CollaborationCreate() {
|
||||
const { user } = useAuth();
|
||||
const [listMaster, setListMaster] = useState<any>([]);
|
||||
const [loadingMaster, setLoadingMaster] = useState(false);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [data, setData] = React.useState<CollaborationCreateProps>({
|
||||
title: "",
|
||||
lokasi: "",
|
||||
purpose: "",
|
||||
benefit: "",
|
||||
projectCollaborationMaster_IndustriId: "",
|
||||
userId: "",
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
onLoadMaster();
|
||||
}, []);
|
||||
|
||||
async function onLoadMaster() {
|
||||
try {
|
||||
setLoadingMaster(true);
|
||||
const response = await apiMasterCollaborationType();
|
||||
setListMaster(response.data);
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
} finally {
|
||||
setLoadingMaster(false);
|
||||
}
|
||||
}
|
||||
|
||||
const handlerSubmit = async () => {
|
||||
if (
|
||||
!data?.title ||
|
||||
!data?.lokasi ||
|
||||
!data?.purpose ||
|
||||
!data?.benefit ||
|
||||
!data?.projectCollaborationMaster_IndustriId
|
||||
) {
|
||||
Toast.show({
|
||||
type: "error",
|
||||
text1: "Gagal",
|
||||
text2: "Harap isi semua data",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const newData: CollaborationCreateProps = {
|
||||
title: data?.title,
|
||||
lokasi: data?.lokasi,
|
||||
purpose: data?.purpose,
|
||||
benefit: data?.benefit,
|
||||
projectCollaborationMaster_IndustriId:
|
||||
data?.projectCollaborationMaster_IndustriId,
|
||||
userId: user?.id,
|
||||
};
|
||||
|
||||
try {
|
||||
setIsLoading(true);
|
||||
|
||||
const response = await apiCollaborationCreate({ data: newData });
|
||||
if (response.success) {
|
||||
Toast.show({
|
||||
type: "success",
|
||||
text1: "Berhasil",
|
||||
text2: response.message,
|
||||
});
|
||||
router.back();
|
||||
} else {
|
||||
Toast.show({
|
||||
type: "error",
|
||||
text1: "Gagal",
|
||||
text2: response.message,
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<ViewWrapper>
|
||||
<StackCustom gap={"xs"}>
|
||||
<TextInputCustom label="Judul" placeholder="Masukan judul" required />
|
||||
<TextInputCustom label="Lokasi" placeholder="Masukan lokasi" required />
|
||||
<SelectCustom
|
||||
label="Pilih Industri"
|
||||
data={[
|
||||
{ label: "Industri 1", value: "industri-1" },
|
||||
{ label: "Industri 2", value: "industri-2" },
|
||||
{ label: "Industri 3", value: "industri-3" },
|
||||
]}
|
||||
onChange={(value) => console.log(value)}
|
||||
/>
|
||||
{loadingMaster ? (
|
||||
<LoaderCustom />
|
||||
) : (
|
||||
<StackCustom gap={"xs"}>
|
||||
<TextInputCustom
|
||||
label="Judul"
|
||||
placeholder="Masukan judul"
|
||||
required
|
||||
value={data?.title}
|
||||
onChangeText={(value: any) => setData({ ...data, title: value })}
|
||||
/>
|
||||
|
||||
<TextAreaCustom
|
||||
required
|
||||
label="Tujuan Proyek"
|
||||
placeholder="Masukan tujuan proyek"
|
||||
showCount
|
||||
maxLength={1000}
|
||||
/>
|
||||
<TextInputCustom
|
||||
label="Lokasi"
|
||||
placeholder="Masukan lokasi"
|
||||
required
|
||||
value={data?.lokasi}
|
||||
onChangeText={(value: any) => setData({ ...data, lokasi: value })}
|
||||
/>
|
||||
|
||||
<TextAreaCustom
|
||||
required
|
||||
label="Keuntungan Proyek"
|
||||
placeholder="Masukan keuntungan proyek"
|
||||
showCount
|
||||
maxLength={1000}
|
||||
/>
|
||||
<SelectCustom
|
||||
label="Pilih Industri"
|
||||
data={listMaster?.map((item: any) => ({
|
||||
label: item.name,
|
||||
value: item.id,
|
||||
}))}
|
||||
value={data?.projectCollaborationMaster_IndustriId}
|
||||
onChange={(value: any) => {
|
||||
console.log(value);
|
||||
setData({
|
||||
...data,
|
||||
projectCollaborationMaster_IndustriId: value,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
|
||||
<ButtonCustom
|
||||
title="Simpan"
|
||||
onPress={() => {
|
||||
console.log("Simpan proyek");
|
||||
router.back();
|
||||
}}
|
||||
/>
|
||||
</StackCustom>
|
||||
<TextAreaCustom
|
||||
required
|
||||
label="Tujuan Proyek"
|
||||
placeholder="Masukan tujuan proyek"
|
||||
showCount
|
||||
maxLength={1000}
|
||||
value={data?.purpose}
|
||||
onChangeText={(value: any) => setData({ ...data, purpose: value })}
|
||||
/>
|
||||
|
||||
<TextAreaCustom
|
||||
required
|
||||
label="Keuntungan Proyek"
|
||||
placeholder="Masukan keuntungan proyek, contoh: Meningkatkan relasi bisnis , menjamin kualitas produk, meningkatkan kinerja dan lain lain"
|
||||
showCount
|
||||
maxLength={1000}
|
||||
value={data?.benefit}
|
||||
onChangeText={(value: any) => setData({ ...data, benefit: value })}
|
||||
/>
|
||||
|
||||
<ButtonCustom
|
||||
isLoading={isLoading}
|
||||
title="Simpan"
|
||||
onPress={() => handlerSubmit()}
|
||||
/>
|
||||
</StackCustom>
|
||||
)}
|
||||
</ViewWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
111
app/(application)/(user)/delete-account.tsx
Normal file
@@ -0,0 +1,111 @@
|
||||
import {
|
||||
AlertDefaultSystem,
|
||||
BaseBox,
|
||||
ButtonCustom,
|
||||
CenterCustom,
|
||||
StackCustom,
|
||||
TextCustom,
|
||||
TextInputCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
import { apiDeleteUser } from "@/service/api-client/api-user";
|
||||
import { Image } from "expo-image";
|
||||
import { useLocalSearchParams } from "expo-router/build/hooks";
|
||||
import { useState } from "react";
|
||||
import Toast from "react-native-toast-message";
|
||||
|
||||
export default function DeleteAccount() {
|
||||
const { token, logout, user } = useAuth();
|
||||
const { phone } = useLocalSearchParams();
|
||||
const [text, setText] = useState("");
|
||||
const [isLoading, setLoading] = useState(false);
|
||||
|
||||
const deleteAccount = async () => {
|
||||
if (text !== "Delete Account") {
|
||||
return Toast.show({
|
||||
type: "error",
|
||||
text1: "Ketik 'Delete Account' untuk menghapus akun",
|
||||
});
|
||||
}
|
||||
|
||||
AlertDefaultSystem({
|
||||
title: "Anda yakin akan menghapus akun ini?",
|
||||
message:
|
||||
"Semua data yang pernah anda buat akan terhapus secara permanen !",
|
||||
textLeft: "Batal",
|
||||
textRight: "Ya",
|
||||
onPressRight: async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const response = await apiDeleteUser({ id: user?.id as string });
|
||||
|
||||
if (response.success) {
|
||||
console.log("RESPONSE >> ", response);
|
||||
Toast.show({
|
||||
type: "success",
|
||||
text1: "Akun berhasil dihapus",
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
logout();
|
||||
setLoading(false);
|
||||
}, 2000);
|
||||
} else {
|
||||
Toast.show({
|
||||
type: "error",
|
||||
text1: "Gagal menghapus akun",
|
||||
});
|
||||
setLoading(false);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("ERROR >> ", error);
|
||||
setLoading(false);
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<ViewWrapper>
|
||||
<StackCustom>
|
||||
<BaseBox>
|
||||
<StackCustom>
|
||||
<CenterCustom>
|
||||
<Image
|
||||
source={require("@/assets/images/constants/logo-hipmi.png")}
|
||||
style={{
|
||||
width: 150,
|
||||
height: 150,
|
||||
}}
|
||||
/>
|
||||
</CenterCustom>
|
||||
<TextCustom align="center">
|
||||
Anda akan menghapus akun dengan nomor +{phone}
|
||||
</TextCustom>
|
||||
<TextCustom align="center">
|
||||
Ketik 'Delete Account' untuk menghapus akun
|
||||
</TextCustom>
|
||||
<TextInputCustom
|
||||
value={text}
|
||||
onChangeText={setText}
|
||||
placeholder="Ketik 'Delete Account'"
|
||||
/>
|
||||
|
||||
<ButtonCustom
|
||||
backgroundColor="red"
|
||||
textColor="white"
|
||||
onPress={deleteAccount}
|
||||
isLoading={isLoading}
|
||||
disabled={isLoading}
|
||||
>
|
||||
Submit
|
||||
</ButtonCustom>
|
||||
</StackCustom>
|
||||
</BaseBox>
|
||||
</StackCustom>
|
||||
</ViewWrapper>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,21 +1,9 @@
|
||||
import {
|
||||
FloatingButton,
|
||||
ViewWrapper
|
||||
} from "@/components";
|
||||
import Donation_BoxPublish from "@/screens/Donation/BoxPublish";
|
||||
import { router } from "expo-router";
|
||||
import Donation_ScreenBeranda from "@/screens/Donation/ScreenBeranda";
|
||||
|
||||
export default function DonationBeranda() {
|
||||
return (
|
||||
<ViewWrapper
|
||||
hideFooter
|
||||
floatingButton={
|
||||
<FloatingButton onPress={() => router.push("/donation/create")} />
|
||||
}
|
||||
>
|
||||
{Array.from({ length: 10 }).map((_, index) => (
|
||||
<Donation_BoxPublish key={index} id={index.toString()}/>
|
||||
))}
|
||||
</ViewWrapper>
|
||||
<>
|
||||
<Donation_ScreenBeranda />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,76 +1,5 @@
|
||||
import {
|
||||
BadgeCustom,
|
||||
BaseBox,
|
||||
DummyLandscapeImage,
|
||||
Grid,
|
||||
StackCustom,
|
||||
TextCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import { dummyMasterStatusTransaction } from "@/lib/dummy-data/_master/status-transaction";
|
||||
import { router } from "expo-router";
|
||||
import { View } from "react-native";
|
||||
import Donation_ScreenMyDonation from "@/screens/Donation/ScreenMyDonation";
|
||||
|
||||
export default function DonationMyDonation() {
|
||||
const randomStatusData = Array.from({ length: 10 }, () => {
|
||||
const randomIndex = Math.floor(
|
||||
Math.random() * dummyMasterStatusTransaction.length
|
||||
);
|
||||
return dummyMasterStatusTransaction[randomIndex];
|
||||
});
|
||||
|
||||
const handlePress = (value: string) => {
|
||||
if (value === "menunggu") {
|
||||
router.push(`/donation/${value}/(transaction-flow)/123/invoice`);
|
||||
} else if (value === "proses") {
|
||||
router.push(`/donation/${value}/(transaction-flow)/123/process`);
|
||||
} else if (value === "berhasil") {
|
||||
router.push(`/donation/${value}/(transaction-flow)/123/success`);
|
||||
} else if (value === "gagal") {
|
||||
router.push(`/donation/${value}/(transaction-flow)/123/failed`);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<ViewWrapper hideFooter>
|
||||
{randomStatusData.map((item, index) => (
|
||||
<BaseBox
|
||||
key={index}
|
||||
paddingTop={7}
|
||||
paddingBottom={7}
|
||||
onPress={() => {
|
||||
handlePress(item.value);
|
||||
}}
|
||||
>
|
||||
<Grid>
|
||||
<Grid.Col span={5}>
|
||||
<DummyLandscapeImage height={100} unClickPath />
|
||||
</Grid.Col>
|
||||
<Grid.Col span={1}>
|
||||
<View />
|
||||
</Grid.Col>
|
||||
<Grid.Col span={6}>
|
||||
<StackCustom gap={"sm"}>
|
||||
<View>
|
||||
<TextCustom truncate>
|
||||
Judul Donasi: Lorem ipsum dolor sit amet consectetur
|
||||
adipisicing elit.
|
||||
</TextCustom>
|
||||
</View>
|
||||
<View>
|
||||
<TextCustom>Donasi Saya</TextCustom>
|
||||
<TextCustom bold color="yellow">
|
||||
Rp. 7.500.000
|
||||
</TextCustom>
|
||||
</View>
|
||||
<BadgeCustom variant="light" color={item.color} fullWidth>
|
||||
{item.label}
|
||||
</BadgeCustom>
|
||||
</StackCustom>
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
</BaseBox>
|
||||
))}
|
||||
</ViewWrapper>
|
||||
);
|
||||
return <Donation_ScreenMyDonation />;
|
||||
}
|
||||
|
||||
@@ -1,38 +1,10 @@
|
||||
import { ScrollableCustom, ViewWrapper } from "@/components";
|
||||
import { dummyMasterStatus } from "@/lib/dummy-data/_master/status";
|
||||
import Donasi_BoxStatus from "@/screens/Donation/BoxStatus";
|
||||
import { useState } from "react";
|
||||
import Donation_ScreenStatus from "@/screens/Donation/ScreenStatus";
|
||||
import { useLocalSearchParams } from "expo-router";
|
||||
|
||||
export default function DonationStatus() {
|
||||
const [activeCategory, setActiveCategory] = useState<string | null>(
|
||||
"publish"
|
||||
);
|
||||
|
||||
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}
|
||||
/>
|
||||
);
|
||||
const { status } = useLocalSearchParams<{ status?: string }>();
|
||||
|
||||
return (
|
||||
<ViewWrapper hideFooter headerComponent={scrollComponent}>
|
||||
{Array.from({ length: 10 }).map((_, index) => (
|
||||
<Donasi_BoxStatus
|
||||
key={index}
|
||||
id={index.toString()}
|
||||
status={activeCategory as string}
|
||||
/>
|
||||
))}
|
||||
</ViewWrapper>
|
||||
<Donation_ScreenStatus initialStatus={status || "publish"} />
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
BoxButtonOnFooter,
|
||||
ButtonCenteredOnly,
|
||||
ButtonCustom,
|
||||
InformationBox,
|
||||
@@ -9,17 +11,134 @@ import {
|
||||
TextInputCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import { router } from "expo-router";
|
||||
import API_STRORAGE from "@/constants/base-url-api-strorage";
|
||||
import DIRECTORY_ID from "@/constants/directory-id";
|
||||
import {
|
||||
apiDonationGetNewsById,
|
||||
apiDonationUpdateNews,
|
||||
} from "@/service/api-client/api-donation";
|
||||
import { uploadFileService } from "@/service/upload-service";
|
||||
import pickFile, { IFileData } from "@/utils/pickFile";
|
||||
import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
|
||||
import { useCallback, useState } from "react";
|
||||
import Toast from "react-native-toast-message";
|
||||
|
||||
export default function DonationEditNews() {
|
||||
const { news } = useLocalSearchParams();
|
||||
const [data, setData] = useState<any>(null);
|
||||
const [image, setImage] = useState<IFileData | null>(null);
|
||||
const [isLoading, setLoading] = useState(false);
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
onLoadData();
|
||||
}, [news]),
|
||||
);
|
||||
|
||||
const onLoadData = async () => {
|
||||
try {
|
||||
const response = await apiDonationGetNewsById({
|
||||
id: news as string,
|
||||
category: "get-one",
|
||||
});
|
||||
|
||||
setData(response.data);
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
}
|
||||
};
|
||||
|
||||
const handlerSubmitUpdate = async () => {
|
||||
let newData;
|
||||
if (!data.title || !data.deskripsi) {
|
||||
Toast.show({
|
||||
type: "error",
|
||||
text1: "Judul dan deskripsi harus diisi",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
setLoading(true);
|
||||
|
||||
newData = {
|
||||
title: data?.title,
|
||||
deskripsi: data?.deskripsi,
|
||||
};
|
||||
|
||||
if (image && image?.uri) {
|
||||
const uploadNewImage = await uploadFileService({
|
||||
dirId: DIRECTORY_ID.donasi_kabar,
|
||||
imageUri: image?.uri,
|
||||
});
|
||||
|
||||
newData = {
|
||||
title: data?.title,
|
||||
deskripsi: data?.deskripsi,
|
||||
newImageId: uploadNewImage.data.id,
|
||||
};
|
||||
}
|
||||
|
||||
const response = await apiDonationUpdateNews({
|
||||
id: news as string,
|
||||
data: newData,
|
||||
});
|
||||
|
||||
if (!response.success) {
|
||||
Toast.show({
|
||||
type: "error",
|
||||
text1: "Gagal mengupdate berita",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
Toast.show({
|
||||
type: "success",
|
||||
text1: "Berita berhasil diperbarui",
|
||||
});
|
||||
router.back();
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
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 />
|
||||
<LandscapeFrameUploaded
|
||||
image={
|
||||
image
|
||||
? image.uri
|
||||
: data && data.imageId
|
||||
? API_STRORAGE.GET({ fileId: data.imageId })
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
<ButtonCenteredOnly
|
||||
onPress={() => {
|
||||
router.push("/(application)/(image)/take-picture/123");
|
||||
pickFile({
|
||||
allowedType: "image",
|
||||
setImageUri(file) {
|
||||
setImage(file);
|
||||
},
|
||||
});
|
||||
}}
|
||||
icon="upload"
|
||||
>
|
||||
@@ -30,6 +149,8 @@ export default function DonationEditNews() {
|
||||
label="Judul Berita"
|
||||
placeholder="Masukan judul berita"
|
||||
required
|
||||
value={data?.title}
|
||||
onChangeText={(value) => setData({ ...data, title: value })}
|
||||
/>
|
||||
<TextAreaCustom
|
||||
label="Deskripsi Berita"
|
||||
@@ -37,16 +158,11 @@ export default function DonationEditNews() {
|
||||
required
|
||||
showCount
|
||||
maxLength={1000}
|
||||
value={data?.deskripsi}
|
||||
onChangeText={(value) => setData({ ...data, deskripsi: value })}
|
||||
/>
|
||||
|
||||
<Spacing />
|
||||
<ButtonCustom
|
||||
onPress={() => {
|
||||
router.back();
|
||||
}}
|
||||
>
|
||||
Update
|
||||
</ButtonCustom>
|
||||
</StackCustom>
|
||||
<Spacing />
|
||||
</ViewWrapper>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
AlertDefaultSystem,
|
||||
BackButton,
|
||||
@@ -12,13 +13,45 @@ import {
|
||||
} from "@/components";
|
||||
import { IconEdit } from "@/components/_Icon";
|
||||
import { IconTrash } from "@/components/_Icon/IconTrash";
|
||||
import dayjs from "dayjs";
|
||||
import { router, Stack, useLocalSearchParams } from "expo-router";
|
||||
import { useState } from "react";
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
import {
|
||||
apiDonationDeleteNews,
|
||||
apiDonationGetNewsById,
|
||||
} from "@/service/api-client/api-donation";
|
||||
import { formatChatTime } from "@/utils/formatChatTime";
|
||||
import {
|
||||
router,
|
||||
Stack,
|
||||
useFocusEffect,
|
||||
useLocalSearchParams,
|
||||
} from "expo-router";
|
||||
import { useCallback, useState } from "react";
|
||||
import Toast from "react-native-toast-message";
|
||||
|
||||
export default function DonationNews() {
|
||||
const { id, news } = useLocalSearchParams();
|
||||
const { user } = useAuth();
|
||||
const { news } = useLocalSearchParams();
|
||||
const [openDrawer, setOpenDrawer] = useState(false);
|
||||
const [data, setData] = useState<any>(null);
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
onLoadData();
|
||||
}, [news])
|
||||
);
|
||||
|
||||
const onLoadData = async () => {
|
||||
try {
|
||||
const response = await apiDonationGetNewsById({
|
||||
id: news as string,
|
||||
category: "get-one",
|
||||
});
|
||||
|
||||
setData(response.data);
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -26,28 +59,28 @@ export default function DonationNews() {
|
||||
options={{
|
||||
title: "Detail Kabar",
|
||||
headerLeft: () => <BackButton />,
|
||||
headerRight: () => <DotButton onPress={() => setOpenDrawer(true)} />,
|
||||
headerRight: () =>
|
||||
user?.id === data?.authorId && (
|
||||
<DotButton onPress={() => setOpenDrawer(true)} />
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<ViewWrapper>
|
||||
<BaseBox>
|
||||
<StackCustom>
|
||||
<TextCustom style={{ alignSelf: "flex-end" }}>
|
||||
{dayjs().format("DD MMM YYYY")}
|
||||
{formatChatTime(data?.createdAt)}
|
||||
</TextCustom>
|
||||
|
||||
<DummyLandscapeImage />
|
||||
{data && data.imageId && (
|
||||
<DummyLandscapeImage imageId={data.imageId} />
|
||||
)}
|
||||
|
||||
<TextCustom bold size="large" align="center">
|
||||
Judul Berita
|
||||
{data?.title || "-"}
|
||||
</TextCustom>
|
||||
|
||||
<TextCustom>
|
||||
Lorem ipsum dolor sit amet consectetur adipisicing elit. Sapiente
|
||||
est id temporibus perferendis eos reiciendis reprehenderit tempora
|
||||
ut quibusdam dolores facilis rerum exercitationem recusandae quis
|
||||
neque, adipisci dolorum, aspernatur labore?
|
||||
</TextCustom>
|
||||
<TextCustom>{data?.deskripsi || "-"}</TextCustom>
|
||||
</StackCustom>
|
||||
</BaseBox>
|
||||
</ViewWrapper>
|
||||
@@ -62,12 +95,12 @@ export default function DonationNews() {
|
||||
{
|
||||
icon: <IconEdit />,
|
||||
label: "Edit Berita",
|
||||
path: `/donation/${id}/(news)/${news}/edit-news` as any,
|
||||
path: `/donation/[id]/(news)/${news}/edit-news` as any,
|
||||
},
|
||||
{
|
||||
icon: <IconTrash />,
|
||||
label: "Hapus Berita",
|
||||
path: `` as any,
|
||||
path: "",
|
||||
color: "red",
|
||||
},
|
||||
]}
|
||||
@@ -79,7 +112,23 @@ export default function DonationNews() {
|
||||
message: "Apakah Anda yakin ingin menghapus berita ini?",
|
||||
textLeft: "Batal",
|
||||
textRight: "Hapus",
|
||||
onPressRight: () => {
|
||||
onPressRight: async () => {
|
||||
const response = await apiDonationDeleteNews({
|
||||
id: news as string,
|
||||
});
|
||||
|
||||
if (!response.success) {
|
||||
Toast.show({
|
||||
type: "error",
|
||||
text1: "Gagal menghapus berita",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
Toast.show({
|
||||
type: "success",
|
||||
text1: "Berita berhasil dihapus",
|
||||
});
|
||||
router.back();
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,25 +1,103 @@
|
||||
import {
|
||||
BoxButtonOnFooter,
|
||||
ButtonCenteredOnly,
|
||||
ButtonCustom,
|
||||
InformationBox,
|
||||
LandscapeFrameUploaded,
|
||||
NewWrapper,
|
||||
Spacing,
|
||||
StackCustom,
|
||||
TextAreaCustom,
|
||||
TextInputCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import { router } from "expo-router";
|
||||
import DIRECTORY_ID from "@/constants/directory-id";
|
||||
import { apiDonationCreateNews } from "@/service/api-client/api-donation";
|
||||
import { uploadFileService } from "@/service/upload-service";
|
||||
import pickFile, { IFileData } from "@/utils/pickFile";
|
||||
import { router, useLocalSearchParams } from "expo-router";
|
||||
import { useState } from "react";
|
||||
import Toast from "react-native-toast-message";
|
||||
|
||||
export default function DonationAddNews() {
|
||||
const { id } = useLocalSearchParams();
|
||||
const [data, setData] = useState({
|
||||
title: "",
|
||||
deskripsi: "",
|
||||
});
|
||||
const [image, setImage] = useState<IFileData | null>(null);
|
||||
const [isLoading, setLoading] = useState(false);
|
||||
|
||||
const handlerSubmit = async () => {
|
||||
let newData: any = { ...data };
|
||||
try {
|
||||
setLoading(true);
|
||||
if (image) {
|
||||
const responseUploadImage = await uploadFileService({
|
||||
dirId: DIRECTORY_ID.donasi_kabar,
|
||||
imageUri: image?.uri,
|
||||
});
|
||||
|
||||
newData = {
|
||||
...newData,
|
||||
imageId: responseUploadImage.data.id,
|
||||
};
|
||||
}
|
||||
|
||||
const response = await apiDonationCreateNews({
|
||||
id: id as string,
|
||||
data: newData,
|
||||
});
|
||||
|
||||
if (!response.success) {
|
||||
Toast.show({
|
||||
type: "error",
|
||||
text1: "Gagal menambah berita",
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
Toast.show({
|
||||
type: "success",
|
||||
text1: "Berita berhasil ditambahkan",
|
||||
});
|
||||
|
||||
router.back();
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
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 />
|
||||
<LandscapeFrameUploaded image={image?.uri} />
|
||||
<ButtonCenteredOnly
|
||||
onPress={() => {
|
||||
router.push("/(application)/(image)/take-picture/123");
|
||||
pickFile({
|
||||
allowedType: "image",
|
||||
setImageUri(file) {
|
||||
setImage(file);
|
||||
},
|
||||
});
|
||||
}}
|
||||
icon="upload"
|
||||
>
|
||||
@@ -30,6 +108,13 @@ export default function DonationAddNews() {
|
||||
label="Judul Berita"
|
||||
placeholder="Masukan judul berita"
|
||||
required
|
||||
value={data.title}
|
||||
onChangeText={(value) => {
|
||||
setData({
|
||||
...data,
|
||||
title: value,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<TextAreaCustom
|
||||
label="Deskripsi Berita"
|
||||
@@ -37,18 +122,17 @@ export default function DonationAddNews() {
|
||||
required
|
||||
showCount
|
||||
maxLength={1000}
|
||||
/>
|
||||
|
||||
<Spacing />
|
||||
<ButtonCustom
|
||||
onPress={() => {
|
||||
router.back();
|
||||
value={data.deskripsi}
|
||||
onChangeText={(value) => {
|
||||
setData({
|
||||
...data,
|
||||
deskripsi: value,
|
||||
});
|
||||
}}
|
||||
>
|
||||
Simpan
|
||||
</ButtonCustom>
|
||||
/>
|
||||
|
||||
<Spacing />
|
||||
</StackCustom>
|
||||
<Spacing />
|
||||
</ViewWrapper>
|
||||
</NewWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,67 +1,8 @@
|
||||
import {
|
||||
BackButton,
|
||||
BaseBox,
|
||||
DrawerCustom,
|
||||
Grid,
|
||||
MenuDrawerDynamicGrid,
|
||||
TextCustom,
|
||||
ViewWrapper
|
||||
} from "@/components";
|
||||
import { IconPlus } from "@/components/_Icon";
|
||||
import dayjs from "dayjs";
|
||||
import { router, Stack, useLocalSearchParams } from "expo-router";
|
||||
import { 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);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Stack.Screen
|
||||
options={{
|
||||
title: "Daftar Kabar",
|
||||
headerLeft: () => <BackButton />, }}
|
||||
/>
|
||||
<ViewWrapper>
|
||||
{Array.from({ length: 15 }).map((_, index) => (
|
||||
<BaseBox key={index} href={`/donation/${id}/(news)/${index}`}>
|
||||
<Grid>
|
||||
<Grid.Col span={8}>
|
||||
<TextCustom truncate bold>
|
||||
Lorem ipsum dolor, sit amet consectetur adipisicing elit.
|
||||
</TextCustom>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={4} style={{ alignItems: "flex-end" }}>
|
||||
<TextCustom size="small">
|
||||
{dayjs().format("DD MMM YYYY")}
|
||||
</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,68 +1,8 @@
|
||||
import {
|
||||
BackButton,
|
||||
BaseBox,
|
||||
DotButton,
|
||||
DrawerCustom,
|
||||
Grid,
|
||||
MenuDrawerDynamicGrid,
|
||||
TextCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import { IconPlus } from "@/components/_Icon";
|
||||
import dayjs from "dayjs";
|
||||
import { router, Stack, useLocalSearchParams } from "expo-router";
|
||||
import { 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);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Stack.Screen
|
||||
options={{
|
||||
title: "Rekap Kabar",
|
||||
headerLeft: () => <BackButton />,
|
||||
headerRight: () => <DotButton onPress={() => setOpenDrawer(true)} />,
|
||||
}}
|
||||
/>
|
||||
<ViewWrapper>
|
||||
{Array.from({ length: 15 }).map((_, index) => (
|
||||
<BaseBox key={index} href={`/donation/${id}/(news)/${index}`}>
|
||||
<Grid>
|
||||
<Grid.Col span={8}>
|
||||
<TextCustom truncate bold>
|
||||
Lorem ipsum dolor, sit amet consectetur adipisicing elit.
|
||||
</TextCustom>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={4} style={{ alignItems: "flex-end" }}>
|
||||
<TextCustom size="small">{dayjs().format("DD MMM YYYY")}</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,3 +1,4 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
BaseBox,
|
||||
Grid,
|
||||
@@ -7,11 +8,60 @@ import {
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import { MainColor } from "@/constants/color-palet";
|
||||
import { apiDonationGetInvoiceById } from "@/service/api-client/api-donation";
|
||||
import { GStyles } from "@/styles/global-styles";
|
||||
import { dateTimeView } from "@/utils/dateTimeView";
|
||||
import { formatCurrencyDisplay } from "@/utils/formatCurrencyDisplay";
|
||||
import { FontAwesome6 } from "@expo/vector-icons";
|
||||
import dayjs from "dayjs";
|
||||
import { useFocusEffect, useLocalSearchParams } from "expo-router";
|
||||
import { useCallback, useState } from "react";
|
||||
|
||||
export default function DonasiFailed() {
|
||||
const { id, invoiceId } = useLocalSearchParams();
|
||||
const [data, setData] = useState<any>(null);
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
onLoadData();
|
||||
}, [id, invoiceId])
|
||||
);
|
||||
|
||||
const onLoadData = async () => {
|
||||
try {
|
||||
const response = await apiDonationGetInvoiceById({
|
||||
id: invoiceId as string,
|
||||
});
|
||||
|
||||
console.log("[DATA]", JSON.stringify(response.data, null, 2));
|
||||
setData(response.data);
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
}
|
||||
};
|
||||
|
||||
const listData = [
|
||||
{
|
||||
label: "Bank",
|
||||
value: (data && data?.MasterBank?.namaBank) || "-",
|
||||
},
|
||||
{
|
||||
label: "Rekening Penerima",
|
||||
value: (data && data?.MasterBank?.namaAkun) || "-",
|
||||
},
|
||||
{
|
||||
label: "No Rekening",
|
||||
value: (data && data?.MasterBank?.norek) || "-",
|
||||
},
|
||||
{
|
||||
label: "Jumlah Donasi",
|
||||
value: (data && formatCurrencyDisplay(data?.nominal)) || "-",
|
||||
},
|
||||
{
|
||||
label: "Tanggal",
|
||||
value: (data && dateTimeView({ date: data?.createdAt })) || "-",
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<ViewWrapper>
|
||||
<StackCustom>
|
||||
@@ -58,26 +108,3 @@ export default function DonasiFailed() {
|
||||
</ViewWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
const listData = [
|
||||
{
|
||||
label: "Bank",
|
||||
value: " BCA",
|
||||
},
|
||||
{
|
||||
label: "Rekening Penerima",
|
||||
value: "Himpunan Pengusaha Muda Indonesia",
|
||||
},
|
||||
{
|
||||
label: "No Rekening",
|
||||
value: "2304235678854332",
|
||||
},
|
||||
{
|
||||
label: "Jumlah Donasi",
|
||||
value: "Rp. 750.000",
|
||||
},
|
||||
{
|
||||
label: "Tanggal",
|
||||
value: `${dayjs(new Date()).format("DD/MM/YYYY")}`,
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,228 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
BaseBox,
|
||||
BoxButtonOnFooter,
|
||||
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 {
|
||||
apiDonationGetInvoiceById,
|
||||
apiDonationUpdateInvoice,
|
||||
} from "@/service/api-client/api-donation";
|
||||
import { uploadFileService } from "@/service/upload-service";
|
||||
import { formatCurrencyDisplay } from "@/utils/formatCurrencyDisplay";
|
||||
import pickFile 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 DonationInvoice() {
|
||||
const { invoiceId } = useLocalSearchParams();
|
||||
console.log("invoiceId", invoiceId);
|
||||
const [data, setData] = useState<any>(null);
|
||||
const [image, setImage] = useState<any>(null);
|
||||
const [isLoading, setLoading] = useState(false);
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
onLoadData();
|
||||
}, [invoiceId]),
|
||||
);
|
||||
|
||||
const onLoadData = async () => {
|
||||
try {
|
||||
const response = await apiDonationGetInvoiceById({
|
||||
id: invoiceId as string,
|
||||
});
|
||||
console.log("[RESPONSE]", JSON.stringify(response, null, 2));
|
||||
setData(response.data);
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
}
|
||||
};
|
||||
|
||||
const handlerUpdateInvoice = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const responseUploadImage = await uploadFileService({
|
||||
dirId: DIRECTORY_ID.donasi_bukti_transfer,
|
||||
imageUri: image?.uri,
|
||||
});
|
||||
|
||||
console.log("[RESPONSE UPLOAD IMAGE]", responseUploadImage);
|
||||
|
||||
if (!responseUploadImage?.data?.id) {
|
||||
Toast.show({
|
||||
type: "error",
|
||||
text1: "Gagal mengunggah bukti transfer",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const fileId = responseUploadImage?.data?.id;
|
||||
|
||||
const response = await apiDonationUpdateInvoice({
|
||||
id: invoiceId as string,
|
||||
fileId: fileId,
|
||||
status: "proses",
|
||||
});
|
||||
|
||||
console.log("[RESPONSE UPDATE]", JSON.stringify(response, null, 2));
|
||||
|
||||
if (!response.success) {
|
||||
Toast.show({
|
||||
type: "error",
|
||||
text1: "Gagal mengunggah bukti transfer",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
Toast.show({
|
||||
type: "success",
|
||||
text1: "Berhasil mengunggah bukti transfer",
|
||||
});
|
||||
router.replace(`/donation/[id]/(transaction-flow)/${invoiceId}/process`);
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<ViewWrapper
|
||||
hideFooter
|
||||
footerComponent={
|
||||
<BoxButtonOnFooter>
|
||||
<ButtonCustom
|
||||
disabled={!image}
|
||||
isLoading={isLoading}
|
||||
onPress={() => {
|
||||
handlerUpdateInvoice();
|
||||
}}
|
||||
>
|
||||
Simpan
|
||||
</ButtonCustom>
|
||||
</BoxButtonOnFooter>
|
||||
}
|
||||
>
|
||||
<StackCustom>
|
||||
<InformationBox
|
||||
text={`Mohon transfer donasi anda ke rekening dibawah`}
|
||||
/>
|
||||
<BaseBox>
|
||||
<StackCustom gap={"xs"}>
|
||||
<TextCustom bold>
|
||||
BANK: {data?.DonasiMaster_Bank?.name}
|
||||
</TextCustom>
|
||||
{/* <TextCustom>{data?.DonasiMaster_Bank?.accountName}</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">
|
||||
{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 bold align="center" size={"small"} color="gray">
|
||||
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>
|
||||
) : null}
|
||||
<ButtonCenteredOnly
|
||||
onPress={() => {
|
||||
pickFile({
|
||||
allowedType: "image",
|
||||
setImageUri(file) {
|
||||
setImage(file);
|
||||
},
|
||||
});
|
||||
}}
|
||||
icon="upload"
|
||||
>
|
||||
Upload
|
||||
</ButtonCenteredOnly>
|
||||
</StackCustom>
|
||||
</BaseBox>
|
||||
</StackCustom>
|
||||
<Spacing />
|
||||
</ViewWrapper>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,13 +1,6 @@
|
||||
import {
|
||||
BaseBox,
|
||||
Grid,
|
||||
StackCustom,
|
||||
TextCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import { MainColor } from "@/constants/color-palet";
|
||||
import { Ionicons } from "@expo/vector-icons";
|
||||
import { ActivityIndicator } from "react-native";
|
||||
import { BaseBox, StackCustom, TextCustom, ViewWrapper } from "@/components";
|
||||
import MoneyTransferAnimation from "@/components/_ShareComponent/MoneyTransferAnimation";
|
||||
import { View } from "react-native";
|
||||
|
||||
export default function DonationProcess() {
|
||||
return (
|
||||
@@ -16,13 +9,16 @@ export default function DonationProcess() {
|
||||
<BaseBox>
|
||||
<StackCustom>
|
||||
<TextCustom align="center" bold>
|
||||
Admin sedang memproses transaksi donasimu
|
||||
Admin sedang memvalidasi data dan bukti transfer anda. Mohon
|
||||
tunggu proses ini selesai.
|
||||
</TextCustom>
|
||||
<ActivityIndicator size="large" color={MainColor.yellow} />
|
||||
<View style={{ alignItems: "center", justifyContent: "center" }}>
|
||||
<MoneyTransferAnimation />
|
||||
</View>
|
||||
</StackCustom>
|
||||
</BaseBox>
|
||||
|
||||
<BaseBox>
|
||||
{/* <BaseBox>
|
||||
<Grid>
|
||||
<Grid.Col span={10} style={{ justifyContent: "center" }}>
|
||||
<TextCustom size="small">
|
||||
@@ -38,7 +34,7 @@ export default function DonationProcess() {
|
||||
/>
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
</BaseBox>
|
||||
</BaseBox> */}
|
||||
</ViewWrapper>
|
||||
</>
|
||||
);
|
||||
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
BaseBox,
|
||||
Grid,
|
||||
@@ -7,11 +8,60 @@ import {
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import { MainColor } from "@/constants/color-palet";
|
||||
import { apiDonationGetInvoiceById } from "@/service/api-client/api-donation";
|
||||
import { GStyles } from "@/styles/global-styles";
|
||||
import { dateTimeView } from "@/utils/dateTimeView";
|
||||
import { formatCurrencyDisplay } from "@/utils/formatCurrencyDisplay";
|
||||
import { FontAwesome6 } from "@expo/vector-icons";
|
||||
import dayjs from "dayjs";
|
||||
import { useFocusEffect, useLocalSearchParams } from "expo-router";
|
||||
import { useCallback, useState } from "react";
|
||||
|
||||
export default function DonationSuccess() {
|
||||
const { id, invoiceId } = useLocalSearchParams();
|
||||
const [data, setData] = useState<any>(null);
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
onLoadData();
|
||||
}, [id, invoiceId])
|
||||
);
|
||||
|
||||
const onLoadData = async () => {
|
||||
try {
|
||||
const response = await apiDonationGetInvoiceById({
|
||||
id: invoiceId as string,
|
||||
});
|
||||
|
||||
console.log("[DATA]", JSON.stringify(response.data, null, 2));
|
||||
setData(response.data);
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
}
|
||||
};
|
||||
|
||||
const listData = [
|
||||
{
|
||||
label: "Bank",
|
||||
value: (data && data?.MasterBank?.namaBank) || "-",
|
||||
},
|
||||
{
|
||||
label: "Rekening Penerima",
|
||||
value: (data && data?.MasterBank?.namaAkun) || "-",
|
||||
},
|
||||
{
|
||||
label: "No Rekening",
|
||||
value: (data && data?.MasterBank?.norek) || "-",
|
||||
},
|
||||
{
|
||||
label: "Jumlah Donasi",
|
||||
value: (data && formatCurrencyDisplay(data?.nominal)) || "-",
|
||||
},
|
||||
{
|
||||
label: "Tanggal",
|
||||
value: (data && dateTimeView({ date: data?.createdAt })) || "-",
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<ViewWrapper>
|
||||
<StackCustom>
|
||||
@@ -59,25 +109,4 @@ export default function DonationSuccess() {
|
||||
);
|
||||
}
|
||||
|
||||
const listData = [
|
||||
{
|
||||
label: "Bank",
|
||||
value: " BCA",
|
||||
},
|
||||
{
|
||||
label: "Rekening Penerima",
|
||||
value: "Himpunan Pengusaha Muda Indonesia",
|
||||
},
|
||||
{
|
||||
label: "No Rekening",
|
||||
value: "2304235678854332",
|
||||
},
|
||||
{
|
||||
label: "Jumlah Donasi",
|
||||
value: "Rp. 750.000",
|
||||
},
|
||||
{
|
||||
label: "Tanggal",
|
||||
value: `${dayjs(new Date()).format("DD/MM/YYYY")}`,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -1,110 +0,0 @@
|
||||
import {
|
||||
BaseBox,
|
||||
ButtonCenteredOnly,
|
||||
ButtonCustom,
|
||||
Grid,
|
||||
InformationBox,
|
||||
Spacing,
|
||||
StackCustom,
|
||||
TextCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import { MainColor } from "@/constants/color-palet";
|
||||
import { router, useLocalSearchParams } from "expo-router";
|
||||
|
||||
export default function DonationInvoice() {
|
||||
const { id, transaction } = useLocalSearchParams();
|
||||
return (
|
||||
<>
|
||||
<ViewWrapper>
|
||||
<StackCustom>
|
||||
<InformationBox text={`Mohon transfer donasi anda ke rekening dibawah dengan Id: ${transaction}`} />
|
||||
<BaseBox>
|
||||
<StackCustom gap={"xs"}>
|
||||
<TextCustom>Nama BANK</TextCustom>
|
||||
<TextCustom>Nama Penerima</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">
|
||||
4567898765433567
|
||||
</TextCustom>
|
||||
</Grid.Col>
|
||||
<Grid.Col
|
||||
span={4}
|
||||
style={{
|
||||
alignItems: "flex-end",
|
||||
}}
|
||||
>
|
||||
<ButtonCustom>Salin</ButtonCustom>
|
||||
</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. 1.000.000
|
||||
</TextCustom>
|
||||
</Grid.Col>
|
||||
<Grid.Col
|
||||
span={4}
|
||||
style={{
|
||||
alignItems: "flex-end",
|
||||
}}
|
||||
>
|
||||
<ButtonCustom>Salin</ButtonCustom>
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
</BaseBox>
|
||||
</StackCustom>
|
||||
</BaseBox>
|
||||
|
||||
<BaseBox>
|
||||
<StackCustom>
|
||||
<TextCustom>Upload bukti transfer anda.</TextCustom>
|
||||
<ButtonCenteredOnly
|
||||
onPress={() => {
|
||||
router.push("/(application)/(image)/take-picture/123");
|
||||
}}
|
||||
icon="upload"
|
||||
>
|
||||
Upload
|
||||
</ButtonCenteredOnly>
|
||||
</StackCustom>
|
||||
</BaseBox>
|
||||
|
||||
<ButtonCustom
|
||||
onPress={() => {
|
||||
router.push(`/donation/${id}/(transaction-flow)/process`);
|
||||
}}
|
||||
>
|
||||
Saya Sudah Transfer
|
||||
</ButtonCustom>
|
||||
</StackCustom>
|
||||
<Spacing />
|
||||
</ViewWrapper>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -9,15 +9,54 @@ import {
|
||||
} from "@/components";
|
||||
import { MainColor } from "@/constants/color-palet";
|
||||
import { ICON_SIZE_SMALL } from "@/constants/constans-value";
|
||||
import { LOCAL_STORAGE_KEY } from "@/constants/local-storage-key";
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
import { formatCurrencyDisplay } from "@/utils/formatCurrencyDisplay";
|
||||
import { Ionicons } from "@expo/vector-icons";
|
||||
import AsyncStorage from "@react-native-async-storage/async-storage";
|
||||
import { router, useLocalSearchParams } from "expo-router";
|
||||
import { useState } from "react";
|
||||
import Toast from "react-native-toast-message";
|
||||
|
||||
export default function InvestmentInputDonation() {
|
||||
const { user } = useAuth();
|
||||
const { id } = useLocalSearchParams();
|
||||
const [nominal, setNominal] = useState<number>(0);
|
||||
|
||||
const handlerSubmit = async () => {
|
||||
if (!user?.id) {
|
||||
Toast.show({
|
||||
type: "error",
|
||||
text1: "User tidak ditemukan",
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await AsyncStorage.setItem(
|
||||
LOCAL_STORAGE_KEY.transactionDonation,
|
||||
JSON.stringify({ nominal: nominal.toString() }),
|
||||
);
|
||||
router.replace(`/donation/${id}/select-bank`);
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
}
|
||||
};
|
||||
|
||||
const displayJumlah = formatCurrencyDisplay(nominal);
|
||||
|
||||
const handleChangeCurrency = (text: string) => {
|
||||
const numeric = text.replace(/\D/g, "");
|
||||
setNominal(Number(numeric));
|
||||
};
|
||||
|
||||
const bottomComponent = (
|
||||
<BoxButtonOnFooter>
|
||||
<ButtonCustom
|
||||
onPress={() => router.replace(`/donation/${id}/select-bank`)}
|
||||
disabled={nominal < 10000 || nominal === 0}
|
||||
onPress={() => {
|
||||
handlerSubmit();
|
||||
}}
|
||||
>
|
||||
Lanjutan
|
||||
</ButtonCustom>
|
||||
@@ -27,7 +66,7 @@ export default function InvestmentInputDonation() {
|
||||
<>
|
||||
<ViewWrapper footerComponent={bottomComponent}>
|
||||
{listData.map((item, i) => (
|
||||
<BaseBox key={i}>
|
||||
<BaseBox key={i} onPress={() => setNominal(item.value)}>
|
||||
<Grid>
|
||||
<Grid.Col span={8}>
|
||||
<TextCustom bold size="large">
|
||||
@@ -48,9 +87,12 @@ export default function InvestmentInputDonation() {
|
||||
|
||||
<BaseBox>
|
||||
<TextInputCustom
|
||||
keyboardType="numeric"
|
||||
label="Nominal lainnya"
|
||||
placeholder="0"
|
||||
iconLeft="Rp."
|
||||
value={displayJumlah}
|
||||
onChangeText={(value) => handleChangeCurrency(value)}
|
||||
/>
|
||||
<TextCustom size="small" color="gray">
|
||||
Minimal donasi Rp. 10.000
|
||||
|
||||
@@ -5,24 +5,84 @@ import {
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import { RadioCustom, RadioGroup } from "@/components/Radio/RadioCustom";
|
||||
import { dummyMasterBank } from "@/lib/dummy-data/_master/bank";
|
||||
import { LOCAL_STORAGE_KEY } from "@/constants/local-storage-key";
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
import { apiDonationCreateInvoice } from "@/service/api-client/api-donation";
|
||||
import { apiMasterBank } from "@/service/api-client/api-master";
|
||||
import AsyncStorage from "@react-native-async-storage/async-storage";
|
||||
import { router, useLocalSearchParams } from "expo-router";
|
||||
import { useState } from "react";
|
||||
import _ from "lodash";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
export default function DonationSelectBank() {
|
||||
const { id, transaction } = useLocalSearchParams();
|
||||
const [value, setValue] = useState<any | number>("");
|
||||
const { user } = useAuth();
|
||||
const { id } = useLocalSearchParams();
|
||||
const [select, setSelect] = useState<any | number>("");
|
||||
const [listBank, setListBank] = useState<any>([]);
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
|
||||
useEffect(() => {
|
||||
loadListBank();
|
||||
}, []);
|
||||
|
||||
const loadListBank = async () => {
|
||||
try {
|
||||
const response = await apiMasterBank();
|
||||
|
||||
setListBank(response.data);
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
setListBank([]);
|
||||
}
|
||||
};
|
||||
|
||||
const handlerSubmit = async () => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
const dataStorage = await AsyncStorage.getItem(
|
||||
LOCAL_STORAGE_KEY.transactionDonation
|
||||
);
|
||||
|
||||
if (dataStorage) {
|
||||
const storage = JSON.parse(dataStorage);
|
||||
const newData = {
|
||||
...storage,
|
||||
bankId: select,
|
||||
authorId: user?.id,
|
||||
};
|
||||
|
||||
const response = await apiDonationCreateInvoice({
|
||||
id: id as string,
|
||||
data: newData,
|
||||
});
|
||||
|
||||
if (response.success) {
|
||||
const invoiceId = response.data.id;
|
||||
|
||||
await AsyncStorage.removeItem(LOCAL_STORAGE_KEY.transactionDonation);
|
||||
|
||||
router.replace(
|
||||
`/(application)/(user)/donation/[id]/(transaction-flow)/${invoiceId}/invoice`
|
||||
);
|
||||
} else {
|
||||
console.log("[FAILED]", response);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const buttonSubmit = () => {
|
||||
return (
|
||||
<>
|
||||
<BoxButtonOnFooter>
|
||||
<ButtonCustom
|
||||
onPress={() =>
|
||||
router.replace(
|
||||
`/(application)/(user)/donation/${id}/(transaction-flow)/${transaction}/invoice`
|
||||
)
|
||||
}
|
||||
isLoading={isLoading}
|
||||
disabled={!select}
|
||||
onPress={() => handlerSubmit()}
|
||||
>
|
||||
Pilih
|
||||
</ButtonCustom>
|
||||
@@ -32,12 +92,14 @@ export default function DonationSelectBank() {
|
||||
};
|
||||
return (
|
||||
<ViewWrapper footerComponent={buttonSubmit()}>
|
||||
<RadioGroup value={value} onChange={setValue}>
|
||||
{dummyMasterBank.map((item) => (
|
||||
<BaseBox key={item.name}>
|
||||
<RadioCustom label={item.name} value={item.code} />
|
||||
</BaseBox>
|
||||
))}
|
||||
<RadioGroup value={select} onChange={setSelect}>
|
||||
{_.isEmpty(listBank)
|
||||
? []
|
||||
: listBank?.map((item: any, index: number) => (
|
||||
<BaseBox key={index}>
|
||||
<RadioCustom label={item.namaBank} value={item.id} />
|
||||
</BaseBox>
|
||||
))}
|
||||
</RadioGroup>
|
||||
</ViewWrapper>
|
||||
);
|
||||
|
||||
@@ -1,28 +1,59 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
BackButton,
|
||||
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";
|
||||
import Donation_ComponentBoxDetailData from "@/screens/Donation/ComponentBoxDetailData";
|
||||
import Donation_ComponentStoryFunrising from "@/screens/Donation/ComponentStoryFunrising";
|
||||
import Donation_ProgressSection from "@/screens/Donation/ProgressSection";
|
||||
import { apiDonationGetOne } from "@/service/api-client/api-donation";
|
||||
import { countDownAndCondition } from "@/utils/countDownAndCondition";
|
||||
import { FontAwesome6 } from "@expo/vector-icons";
|
||||
import { router, Stack, useLocalSearchParams } from "expo-router";
|
||||
import {
|
||||
router,
|
||||
Stack,
|
||||
useFocusEffect,
|
||||
useLocalSearchParams,
|
||||
} from "expo-router";
|
||||
import _ from "lodash";
|
||||
import { useState } from "react";
|
||||
import { useCallback, 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 [refreshing, setRefreshing] = useState(false);
|
||||
const [data, setData] = useState<any | null>(null);
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
onLoadData();
|
||||
}, [id]),
|
||||
);
|
||||
|
||||
const onLoadData = async () => {
|
||||
try {
|
||||
const response = await apiDonationGetOne({
|
||||
id: id as string,
|
||||
category: "permanent",
|
||||
});
|
||||
|
||||
setData(response.data);
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
}
|
||||
};
|
||||
|
||||
const handlePress = (item: IMenuDrawerItem) => {
|
||||
console.log("PATH ", item.path);
|
||||
@@ -30,6 +61,38 @@ export default function DonasiDetailStatus() {
|
||||
setOpenDrawer(false);
|
||||
};
|
||||
|
||||
const [value, setValue] = useState({
|
||||
sisa: 0,
|
||||
reminder: false,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
updateCountDown();
|
||||
}, [data]);
|
||||
|
||||
const updateCountDown = () => {
|
||||
const countDown = countDownAndCondition({
|
||||
duration: data?.DonasiMaster_Durasi?.name,
|
||||
publishTime: data?.publishTime,
|
||||
});
|
||||
|
||||
setValue({
|
||||
sisa: countDown.durationDay,
|
||||
reminder: countDown.reminder,
|
||||
});
|
||||
};
|
||||
|
||||
const onRefresh = useCallback(() => {
|
||||
try {
|
||||
setRefreshing(true);
|
||||
onLoadData();
|
||||
} catch (error) {
|
||||
console.log("Error refresh");
|
||||
} finally {
|
||||
setRefreshing(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Stack.Screen
|
||||
@@ -44,17 +107,50 @@ export default function DonasiDetailStatus() {
|
||||
) : null,
|
||||
}}
|
||||
/>
|
||||
<ViewWrapper>
|
||||
<Donation_ComponentBoxDetailData
|
||||
bottomSection={
|
||||
status === "publish" && <Donation_ProgressSection id={id as string} />
|
||||
}
|
||||
/>
|
||||
<Donation_ComponentStoryFunrising id={id as string} />
|
||||
<Spacing />
|
||||
<Donation_ButtonStatusSection status={status as string} />
|
||||
<Spacing />
|
||||
</ViewWrapper>
|
||||
<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}
|
||||
status={status as string}
|
||||
/>
|
||||
)}
|
||||
|
||||
<Spacing />
|
||||
</>
|
||||
)}
|
||||
</NewWrapper>
|
||||
|
||||
<DrawerCustom
|
||||
isVisible={openDrawer}
|
||||
|
||||
@@ -1,27 +1,42 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
DummyLandscapeImage,
|
||||
StackCustom,
|
||||
TextCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import { useLocalSearchParams } from "expo-router";
|
||||
import { apiDonationGetOne } from "@/service/api-client/api-donation";
|
||||
import { useFocusEffect, useLocalSearchParams } from "expo-router";
|
||||
import { useCallback, useState } from "react";
|
||||
|
||||
export default function DonationDetailStory() {
|
||||
const { id } = useLocalSearchParams();
|
||||
const [data, setData] = useState<any>();
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
onLoadData();
|
||||
}, [id])
|
||||
);
|
||||
|
||||
const onLoadData = async () => {
|
||||
try {
|
||||
const response = await apiDonationGetOne({
|
||||
id: id as string,
|
||||
category: "permanent",
|
||||
});
|
||||
|
||||
setData(response.data.CeritaDonasi);
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
}
|
||||
};
|
||||
return (
|
||||
<ViewWrapper>
|
||||
<StackCustom>
|
||||
<TextCustom>
|
||||
Lorem {id} ipsum dolor, sit amet consectetur adipisicing elit. Fuga
|
||||
quasi nam nesciunt nisi corporis alias modi, pariatur sit totam rem
|
||||
fugiat ex similique magni, aliquam maiores officiis iure at adipisci.
|
||||
</TextCustom>
|
||||
<DummyLandscapeImage />
|
||||
<TextCustom>
|
||||
Lorem {id} ipsum dolor, sit amet consectetur adipisicing elit. Fuga
|
||||
quasi nam nesciunt nisi corporis alias modi, pariatur sit totam rem
|
||||
fugiat ex similique magni, aliquam maiores officiis iure at adipisci.
|
||||
</TextCustom>
|
||||
<TextCustom>{data?.pembukaan || "-"}</TextCustom>
|
||||
<DummyLandscapeImage imageId={data?.imageId} />
|
||||
<TextCustom>{data?.cerita || "-"}</TextCustom>
|
||||
</StackCustom>
|
||||
</ViewWrapper>
|
||||
);
|
||||
|
||||
@@ -1,7 +1,80 @@
|
||||
import { ViewWrapper, StackCustom, InformationBox, TextInputCustom, Spacing, ButtonCustom } from "@/components";
|
||||
import { router } from "expo-router";
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
ViewWrapper,
|
||||
StackCustom,
|
||||
InformationBox,
|
||||
TextInputCustom,
|
||||
Spacing,
|
||||
ButtonCustom,
|
||||
} from "@/components";
|
||||
import {
|
||||
apiDonationGetOne,
|
||||
apiDonationUpdateData,
|
||||
} from "@/service/api-client/api-donation";
|
||||
import { router, useLocalSearchParams } from "expo-router";
|
||||
import { useEffect, useState } from "react";
|
||||
import Toast from "react-native-toast-message";
|
||||
|
||||
export default function DonationEditRekening() {
|
||||
const { id } = useLocalSearchParams();
|
||||
const [data, setData] = useState({
|
||||
namaBank: "",
|
||||
rekening: "",
|
||||
});
|
||||
const [isLoading, setLoading] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
onLoadData();
|
||||
}, [id]);
|
||||
|
||||
const onLoadData = async () => {
|
||||
try {
|
||||
const response = await apiDonationGetOne({
|
||||
id: id as string,
|
||||
category: "permanent",
|
||||
});
|
||||
|
||||
const resData = response.data;
|
||||
console.log("[RESPONSE]", JSON.stringify(resData, null, 2));
|
||||
|
||||
setData({
|
||||
namaBank: resData.namaBank,
|
||||
rekening: resData.rekening,
|
||||
});
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
}
|
||||
};
|
||||
|
||||
const handlerSubmitUpdate = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
|
||||
const response = await apiDonationUpdateData({
|
||||
id: id as string,
|
||||
data: data,
|
||||
category: "edit-bank-account",
|
||||
});
|
||||
|
||||
if (!response.success) {
|
||||
Toast.show({
|
||||
type: "error",
|
||||
text1: "Gagal mengupdate data bank",
|
||||
});
|
||||
}
|
||||
|
||||
Toast.show({
|
||||
type: "success",
|
||||
text1: "Data bank berhasil diupdate",
|
||||
});
|
||||
router.back();
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<ViewWrapper>
|
||||
<StackCustom gap={"xs"}>
|
||||
@@ -10,17 +83,22 @@ export default function DonationEditRekening() {
|
||||
label="Nama Bank"
|
||||
placeholder="Masukkan nama bank"
|
||||
required
|
||||
value={data.namaBank}
|
||||
onChangeText={(value) => setData({ ...data, namaBank: value })}
|
||||
/>
|
||||
<TextInputCustom
|
||||
label="Nomor Rekening"
|
||||
placeholder="Masukkan nomor rekening"
|
||||
required
|
||||
value={data.rekening}
|
||||
onChangeText={(value) => setData({ ...data, rekening: value })}
|
||||
/>
|
||||
|
||||
<Spacing />
|
||||
<ButtonCustom
|
||||
isLoading={isLoading}
|
||||
onPress={() => {
|
||||
router.back();
|
||||
handlerSubmitUpdate();
|
||||
}}
|
||||
>
|
||||
Update
|
||||
@@ -29,4 +107,4 @@ export default function DonationEditRekening() {
|
||||
<Spacing />
|
||||
</ViewWrapper>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,97 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
ButtonCenteredOnly,
|
||||
ButtonCustom,
|
||||
InformationBox,
|
||||
LandscapeFrameUploaded,
|
||||
Spacing,
|
||||
StackCustom,
|
||||
TextAreaCustom,
|
||||
ViewWrapper
|
||||
ButtonCenteredOnly,
|
||||
ButtonCustom,
|
||||
InformationBox,
|
||||
LandscapeFrameUploaded,
|
||||
Spacing,
|
||||
StackCustom,
|
||||
TextAreaCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import { router } from "expo-router";
|
||||
import API_IMAGE from "@/constants/api-storage";
|
||||
import DIRECTORY_ID from "@/constants/directory-id";
|
||||
import {
|
||||
apiDonationGetOne,
|
||||
apiDonationUpdateData,
|
||||
} from "@/service/api-client/api-donation";
|
||||
import { uploadFileService } from "@/service/upload-service";
|
||||
import pickFile from "@/utils/pickFile";
|
||||
import { router, useLocalSearchParams } from "expo-router";
|
||||
import { useEffect, useState } from "react";
|
||||
import Toast from "react-native-toast-message";
|
||||
|
||||
export default function DonationEditStory() {
|
||||
const { id } = useLocalSearchParams();
|
||||
const [data, setData] = useState<any>();
|
||||
const [imageStory, setImageStory] = useState<string | null>(null);
|
||||
const [isLoading, setLoading] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
onLoadData();
|
||||
}, [id]);
|
||||
|
||||
const onLoadData = async () => {
|
||||
try {
|
||||
const response = await apiDonationGetOne({
|
||||
id: id as string,
|
||||
category: "permanent",
|
||||
});
|
||||
|
||||
setData(response.data.CeritaDonasi);
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
}
|
||||
};
|
||||
|
||||
const handlerSubmitUpdate = async () => {
|
||||
let newData;
|
||||
try {
|
||||
setLoading(true);
|
||||
|
||||
newData = {
|
||||
...data,
|
||||
};
|
||||
|
||||
if (imageStory) {
|
||||
const responseUploadImageDonasi = await uploadFileService({
|
||||
imageUri: imageStory,
|
||||
dirId: DIRECTORY_ID.donasi_cerita_image,
|
||||
});
|
||||
|
||||
newData = {
|
||||
...data,
|
||||
newImageId: responseUploadImageDonasi.data.id,
|
||||
};
|
||||
}
|
||||
|
||||
const response = await apiDonationUpdateData({
|
||||
id: id as string,
|
||||
data: newData,
|
||||
category: "edit-story",
|
||||
});
|
||||
|
||||
console.log("[RESPONSE]", JSON.stringify(response, null, 2));
|
||||
|
||||
if (!response.success) {
|
||||
Toast.show({
|
||||
type: "error",
|
||||
text1: "Gagal membuat donasi",
|
||||
});
|
||||
}
|
||||
|
||||
Toast.show({
|
||||
type: "success",
|
||||
text1: "Donasi berhasil disimpan",
|
||||
});
|
||||
router.back();
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<ViewWrapper>
|
||||
<StackCustom gap={"xs"}>
|
||||
@@ -21,12 +102,23 @@ export default function DonationEditStory() {
|
||||
required
|
||||
showCount
|
||||
maxLength={1000}
|
||||
value={data?.pembukaan}
|
||||
onChangeText={(value) => setData({ ...data, pembukaan: value })}
|
||||
/>
|
||||
|
||||
<LandscapeFrameUploaded />
|
||||
<LandscapeFrameUploaded
|
||||
image={
|
||||
imageStory ? imageStory : API_IMAGE.GET({ fileId: data?.imageId })
|
||||
}
|
||||
/>
|
||||
<ButtonCenteredOnly
|
||||
onPress={() => {
|
||||
router.push("/(application)/(image)/take-picture/123");
|
||||
pickFile({
|
||||
allowedType: "image",
|
||||
setImageUri: ({ uri }) => {
|
||||
setImageStory(uri);
|
||||
},
|
||||
});
|
||||
}}
|
||||
icon="upload"
|
||||
>
|
||||
@@ -39,12 +131,15 @@ export default function DonationEditStory() {
|
||||
required
|
||||
showCount
|
||||
maxLength={1000}
|
||||
value={data?.cerita}
|
||||
onChangeText={(value) => setData({ ...data, cerita: value })}
|
||||
/>
|
||||
|
||||
<Spacing height={40} />
|
||||
<ButtonCustom
|
||||
isLoading={isLoading}
|
||||
onPress={() => {
|
||||
router.back();
|
||||
handlerSubmitUpdate();
|
||||
}}
|
||||
>
|
||||
Update
|
||||
|
||||
@@ -1,79 +1,284 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
ButtonCenteredOnly,
|
||||
ButtonCustom,
|
||||
InformationBox,
|
||||
LandscapeFrameUploaded,
|
||||
SelectCustom,
|
||||
Spacing,
|
||||
StackCustom,
|
||||
TextInputCustom,
|
||||
ViewWrapper,
|
||||
BoxButtonOnFooter,
|
||||
ButtonCenteredOnly,
|
||||
ButtonCustom,
|
||||
InformationBox,
|
||||
LandscapeFrameUploaded,
|
||||
LoaderCustom,
|
||||
NewWrapper,
|
||||
SelectCustom,
|
||||
Spacing,
|
||||
StackCustom,
|
||||
TextInputCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import { dummyDonasiDurasi } from "@/lib/dummy-data/donasi/durasi";
|
||||
import { dummyDonasiKategori } from "@/lib/dummy-data/donasi/kategori";
|
||||
import { router } from "expo-router";
|
||||
import ListSkeletonComponent from "@/components/_ShareComponent/ListSkeletonComponent";
|
||||
import API_IMAGE from "@/constants/api-storage";
|
||||
import DIRECTORY_ID from "@/constants/directory-id";
|
||||
import {
|
||||
apiDonationGetOne,
|
||||
apiDonationUpdateData,
|
||||
} from "@/service/api-client/api-donation";
|
||||
import { apiMasterDonation } from "@/service/api-client/api-master";
|
||||
import { uploadFileService } from "@/service/upload-service";
|
||||
import { formatCurrencyDisplay } from "@/utils/formatCurrencyDisplay";
|
||||
import pickFile from "@/utils/pickFile";
|
||||
import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
|
||||
import _ from "lodash";
|
||||
import { useCallback, useState } from "react";
|
||||
import Toast from "react-native-toast-message";
|
||||
|
||||
interface IEditDonation {
|
||||
donasiMaster_KategoriId: string;
|
||||
donasiMaster_DurasiId: string;
|
||||
title: string;
|
||||
target: string;
|
||||
imageId: string;
|
||||
}
|
||||
|
||||
export default function DonationEdit() {
|
||||
const { id } = useLocalSearchParams();
|
||||
const [data, setData] = useState<IEditDonation>({
|
||||
donasiMaster_DurasiId: "",
|
||||
donasiMaster_KategoriId: "",
|
||||
title: "",
|
||||
target: "",
|
||||
imageId: "",
|
||||
});
|
||||
|
||||
const [image, setImage] = useState<string | null>(null);
|
||||
const [listCategory, setListCategory] = useState<any[]>([]);
|
||||
const [listDuration, setListDuration] = useState<any[]>([]);
|
||||
const [loadList, setLoadList] = useState<boolean>(false);
|
||||
const [isLoading, setLoading] = useState(false);
|
||||
|
||||
const displayTarget = formatCurrencyDisplay(data?.target);
|
||||
const handleChangeCurrency = (field: keyof typeof data) => (text: string) => {
|
||||
const numeric = text.replace(/\D/g, "");
|
||||
setData((prev: any) => ({ ...prev, [field]: numeric }));
|
||||
};
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
onLoadData();
|
||||
onLoadList();
|
||||
}, [id]),
|
||||
);
|
||||
|
||||
const onLoadData = async () => {
|
||||
try {
|
||||
const response = await apiDonationGetOne({
|
||||
id: id as string,
|
||||
category: "permanent",
|
||||
});
|
||||
|
||||
if (response.success) {
|
||||
setData({
|
||||
donasiMaster_DurasiId: response.data.donasiMaster_DurasiId,
|
||||
donasiMaster_KategoriId: response.data.donasiMaster_KategoriId,
|
||||
title: response.data.title,
|
||||
target: response.data.target,
|
||||
imageId: response.data.imageId,
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
}
|
||||
};
|
||||
|
||||
const onLoadList = async () => {
|
||||
try {
|
||||
setLoadList(true);
|
||||
const response = await apiMasterDonation({ category: "" });
|
||||
|
||||
setListCategory(response.data.category);
|
||||
setListDuration(response.data.duration);
|
||||
} catch (error) {
|
||||
console.log(["ERROR"], error);
|
||||
setListCategory([]);
|
||||
setListDuration([]);
|
||||
} finally {
|
||||
setLoadList(false);
|
||||
}
|
||||
};
|
||||
|
||||
const validateData = async () => {
|
||||
if (
|
||||
!data.donasiMaster_DurasiId ||
|
||||
!data.donasiMaster_KategoriId ||
|
||||
!data.title ||
|
||||
!data.target ||
|
||||
!data.imageId
|
||||
) {
|
||||
Toast.show({
|
||||
type: "error",
|
||||
text1: "Harap lengkapi data",
|
||||
});
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
const handlerSubmitUpdate = async () => {
|
||||
const isValid = await validateData();
|
||||
if (!isValid) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
let newData;
|
||||
|
||||
newData = {
|
||||
...data,
|
||||
};
|
||||
setLoading(true);
|
||||
|
||||
if (image && image) {
|
||||
const uploadNewImage = await uploadFileService({
|
||||
dirId: DIRECTORY_ID.donasi_image,
|
||||
imageUri: image,
|
||||
});
|
||||
|
||||
if (!uploadFileService) {
|
||||
Toast.show({
|
||||
type: "error",
|
||||
text1: "Gagal mengunggah gambar",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
newData = {
|
||||
...data,
|
||||
newImageId: uploadNewImage.data.id,
|
||||
};
|
||||
}
|
||||
|
||||
const response = await apiDonationUpdateData({
|
||||
id: id as string,
|
||||
data: newData,
|
||||
category: "edit-donation",
|
||||
});
|
||||
|
||||
if (!response.success) {
|
||||
Toast.show({
|
||||
type: "error",
|
||||
text1: response.message,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
Toast.show({
|
||||
type: "success",
|
||||
text1: "Donasi berhasil diperbarui",
|
||||
});
|
||||
|
||||
router.back();
|
||||
} catch (error) {
|
||||
console.log("[ERROR UPDATE DONASI]", error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<ViewWrapper>
|
||||
<StackCustom gap={"xs"}>
|
||||
<InformationBox text="Lengkapi semua data di bawah untuk selanjutnya mengisi cerita penggalangan dana." />
|
||||
<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 ? (
|
||||
<ListSkeletonComponent />
|
||||
) : (
|
||||
<StackCustom gap={"xs"}>
|
||||
<TextInputCustom
|
||||
label="Judul Donasi"
|
||||
placeholder="Masukkan Judul Donasi"
|
||||
required
|
||||
value={data?.title}
|
||||
onChangeText={(value) => setData({ ...data, title: value })}
|
||||
/>
|
||||
<TextInputCustom
|
||||
iconLeft="Rp."
|
||||
label="Target Donasi"
|
||||
placeholder="Masukkan Target Donasi"
|
||||
required
|
||||
keyboardType="numeric"
|
||||
value={displayTarget}
|
||||
onChangeText={handleChangeCurrency("target")}
|
||||
/>
|
||||
|
||||
<TextInputCustom
|
||||
label="Judul Donasi"
|
||||
placeholder="Masukkan Judul Donasi"
|
||||
required
|
||||
/>
|
||||
<TextInputCustom
|
||||
label="Target Donasi"
|
||||
placeholder="Masukkan Target Donasi"
|
||||
required
|
||||
keyboardType="numeric"
|
||||
/>
|
||||
<LandscapeFrameUploaded
|
||||
image={image ? image : API_IMAGE.GET({ fileId: data?.imageId })}
|
||||
/>
|
||||
<ButtonCenteredOnly
|
||||
onPress={() => {
|
||||
pickFile({
|
||||
setImageUri: ({ uri }) => {
|
||||
setImage(uri);
|
||||
},
|
||||
allowedType: "image",
|
||||
});
|
||||
}}
|
||||
icon="upload"
|
||||
>
|
||||
Upload
|
||||
</ButtonCenteredOnly>
|
||||
<Spacing />
|
||||
|
||||
<LandscapeFrameUploaded />
|
||||
<ButtonCenteredOnly
|
||||
onPress={() => {
|
||||
router.push("/(application)/(image)/take-picture/123");
|
||||
}}
|
||||
icon="upload"
|
||||
>
|
||||
Upload
|
||||
</ButtonCenteredOnly>
|
||||
<Spacing />
|
||||
<SelectCustom
|
||||
data={
|
||||
_.isEmpty(listCategory)
|
||||
? []
|
||||
: listCategory?.map((item) => ({
|
||||
label: item.name,
|
||||
value: item.id,
|
||||
}))
|
||||
}
|
||||
label="Pilih Kategori Donasi"
|
||||
placeholder="Pilih Kategori Donasi"
|
||||
required
|
||||
value={data?.donasiMaster_KategoriId}
|
||||
onChange={(value: any) =>
|
||||
setData({ ...data, donasiMaster_KategoriId: value })
|
||||
}
|
||||
/>
|
||||
|
||||
<SelectCustom
|
||||
data={dummyDonasiKategori.map((item) => ({
|
||||
label: item.label,
|
||||
value: item.value,
|
||||
}))}
|
||||
onChange={(value) => console.log(value)}
|
||||
label="Pilih Kategori Donasi"
|
||||
placeholder="Pilih Kategori Donasi"
|
||||
required
|
||||
/>
|
||||
<SelectCustom
|
||||
data={
|
||||
_.isEmpty(listDuration)
|
||||
? []
|
||||
: listDuration?.map((item) => ({
|
||||
label: item.name + " hari",
|
||||
value: item.id,
|
||||
}))
|
||||
}
|
||||
label="Pilih Durasi Donasi"
|
||||
placeholder="Pilih Durasi Donasi"
|
||||
required
|
||||
value={data?.donasiMaster_DurasiId}
|
||||
onChange={(value: any) =>
|
||||
setData({ ...data, donasiMaster_DurasiId: value })
|
||||
}
|
||||
/>
|
||||
|
||||
<SelectCustom
|
||||
data={dummyDonasiDurasi.map((item) => ({
|
||||
label: item.label,
|
||||
value: item.value,
|
||||
}))}
|
||||
onChange={(value) => console.log(value)}
|
||||
label="Pilih Durasi Donasi"
|
||||
placeholder="Pilih Durasi Donasi"
|
||||
required
|
||||
/>
|
||||
|
||||
<Spacing />
|
||||
<ButtonCustom
|
||||
onPress={() => {
|
||||
router.back();
|
||||
}}
|
||||
>
|
||||
Update
|
||||
</ButtonCustom>
|
||||
</StackCustom>
|
||||
<Spacing />
|
||||
</StackCustom>
|
||||
)}
|
||||
<Spacing />
|
||||
</ViewWrapper>
|
||||
</NewWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,67 +1,8 @@
|
||||
import {
|
||||
BaseBox,
|
||||
ButtonCenteredOnly,
|
||||
Grid,
|
||||
InformationBox,
|
||||
StackCustom,
|
||||
TextCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import dayjs from "dayjs";
|
||||
import { router, useLocalSearchParams } from "expo-router";
|
||||
import { useLocalSearchParams } from "expo-router";
|
||||
import Donation_ScreenFundDisbursement from "@/screens/Donation/ScreenFundDisbursement";
|
||||
|
||||
export default function DonationFundDisbursement() {
|
||||
const { id } = useLocalSearchParams();
|
||||
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. 0
|
||||
</TextCustom>
|
||||
<TextCustom size="small">Total Pencairan Dana</TextCustom>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={6}>
|
||||
<TextCustom bold color="yellow">
|
||||
0 kali
|
||||
</TextCustom>
|
||||
<TextCustom size="small">Akumulasi Pencairan</TextCustom>
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
</BaseBox>
|
||||
|
||||
{Array.from({ length: 10 }).map((_, index) => (
|
||||
<BaseBox key={index}>
|
||||
<StackCustom>
|
||||
<Grid>
|
||||
<Grid.Col span={8}>
|
||||
<TextCustom bold>Pencairan ke - {index + 1}</TextCustom>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={4} style={{ alignItems: "flex-end" }}>
|
||||
<TextCustom>{dayjs().format("DD MMM YYYY")}</TextCustom>
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
<TextCustom>
|
||||
Lorem ipsum dolor sit amet consectetur adipisicing elit.
|
||||
Nesciunt dolor ad sit? Eaque rem nihil natus, id, esse possimus
|
||||
perferendis provident velit illo consectetur distinctio ab
|
||||
accusantium quis earum omnis!
|
||||
</TextCustom>
|
||||
<ButtonCenteredOnly
|
||||
onPress={() => {
|
||||
router.navigate(`/(application)/(file)/${id}`);
|
||||
}}
|
||||
icon="file-text"
|
||||
>
|
||||
Bukti Transaksi
|
||||
</ButtonCenteredOnly>
|
||||
</StackCustom>
|
||||
</BaseBox>
|
||||
))}
|
||||
</ViewWrapper>
|
||||
</>
|
||||
);
|
||||
|
||||
return <Donation_ScreenFundDisbursement donationId={id as string} />;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
BackButton,
|
||||
BoxButtonOnFooter,
|
||||
@@ -5,30 +6,81 @@ 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";
|
||||
import Donation_ComponentStoryFunrising from "@/screens/Donation/ComponentStoryFunrising";
|
||||
import Donation_ProgressSection from "@/screens/Donation/ProgressSection";
|
||||
import { router, Stack, useLocalSearchParams } from "expo-router";
|
||||
import { useState } from "react";
|
||||
import { apiDonationGetOne } from "@/service/api-client/api-donation";
|
||||
import { countDownAndCondition } from "@/utils/countDownAndCondition";
|
||||
import {
|
||||
router,
|
||||
Stack,
|
||||
useFocusEffect,
|
||||
useLocalSearchParams,
|
||||
} from "expo-router";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
|
||||
export default function DonasiDetailBeranda() {
|
||||
const { user } = useAuth();
|
||||
const { id } = useLocalSearchParams();
|
||||
const [openDrawer, setOpenDrawer] = useState(false);
|
||||
const [data, setData] = useState<any>();
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
onLoadData();
|
||||
}, [id]),
|
||||
);
|
||||
|
||||
const onLoadData = async () => {
|
||||
try {
|
||||
const response = await apiDonationGetOne({
|
||||
id: id as string,
|
||||
category: "permanent",
|
||||
});
|
||||
|
||||
setData(response.data);
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
}
|
||||
};
|
||||
|
||||
const [value, setValue] = useState({
|
||||
sisa: 0,
|
||||
reminder: false,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
updateCountDown();
|
||||
}, [data]);
|
||||
|
||||
const updateCountDown = () => {
|
||||
const countDown = countDownAndCondition({
|
||||
duration: data?.DonasiMaster_Durasi?.name,
|
||||
publishTime: data?.publishTime,
|
||||
});
|
||||
|
||||
setValue({
|
||||
sisa: countDown.durationDay,
|
||||
reminder: countDown.reminder,
|
||||
});
|
||||
};
|
||||
|
||||
const buttonSection = (
|
||||
<>
|
||||
<BoxButtonOnFooter>
|
||||
<ButtonCustom
|
||||
onPress={() =>
|
||||
router.navigate(`/donation/${id}/(transaction-flow)`)
|
||||
}
|
||||
disabled={value?.reminder || !data}
|
||||
onPress={() => router.navigate(`/donation/${id}/(transaction-flow)`)}
|
||||
>
|
||||
Donasi
|
||||
{!data ? "Loading..." : value?.reminder ? "Waktu berakhir" : "Donasi"}
|
||||
</ButtonCustom>
|
||||
</BoxButtonOnFooter>
|
||||
</>
|
||||
@@ -40,18 +92,36 @@ export default function DonasiDetailBeranda() {
|
||||
options={{
|
||||
title: `Detail Donasi`,
|
||||
headerLeft: () => <BackButton />,
|
||||
headerRight: () => <DotButton onPress={() => setOpenDrawer(true)} />,
|
||||
headerRight: () =>
|
||||
user?.id === data?.Author?.id ? (
|
||||
<DotButton onPress={() => setOpenDrawer(true)} />
|
||||
) : null,
|
||||
}}
|
||||
/>
|
||||
<ViewWrapper footerComponent={buttonSection}>
|
||||
<StackCustom>
|
||||
<Donation_ComponentBoxDetailData
|
||||
bottomSection={<Donation_ProgressSection id={id as string} />}
|
||||
/>
|
||||
<Donation_ComponentInfoFundrising id={id as string} />
|
||||
<Donation_ComponentStoryFunrising id={id as string} />
|
||||
</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,32 +1,68 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
AvatarCustom,
|
||||
AvatarComp,
|
||||
BaseBox,
|
||||
ButtonCustom,
|
||||
CenterCustom,
|
||||
Grid,
|
||||
LoaderCustom,
|
||||
Spacing,
|
||||
TextCustom,
|
||||
ViewWrapper
|
||||
} from "@/components";
|
||||
import Donation_BoxPublish from "@/screens/Donation/BoxPublish";
|
||||
import React from "react";
|
||||
import { apiDonationFundrising } from "@/service/api-client/api-donation";
|
||||
import { useFocusEffect, useLocalSearchParams } from "expo-router";
|
||||
import _ from "lodash";
|
||||
import React, { useCallback, useState } from "react";
|
||||
import { View } from "react-native";
|
||||
|
||||
export default function DonationInformationFunrising() {
|
||||
const { id } = useLocalSearchParams();
|
||||
const [data, setData] = useState<any>();
|
||||
const [list, setList] = useState<any[] | null>(null);
|
||||
const [loadList, setLoadList] = useState(false);
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
onLoadData();
|
||||
}, [id])
|
||||
);
|
||||
|
||||
const onLoadData = async () => {
|
||||
try {
|
||||
setLoadList(true);
|
||||
const response = await apiDonationFundrising({ id: id as string });
|
||||
|
||||
setData(response?.data?.user);
|
||||
setList(response?.data?.donasi);
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
} finally {
|
||||
setLoadList(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<ViewWrapper>
|
||||
<BaseBox>
|
||||
<Grid>
|
||||
<Grid.Col span={6} style={{ justifyContent: "center" }}>
|
||||
<CenterCustom>
|
||||
<AvatarCustom size="lg" />
|
||||
<View
|
||||
style={{
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
gap: 10,
|
||||
}}
|
||||
>
|
||||
<AvatarComp size="lg" fileId={data?.Profile?.imageId} />
|
||||
<TextCustom bold size="large" truncate>
|
||||
@Username
|
||||
@{data?.username}
|
||||
</TextCustom>
|
||||
</CenterCustom>
|
||||
</View>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={6} style={{ justifyContent: "center" }}>
|
||||
<ButtonCustom href={`/profile/1234`}>
|
||||
<ButtonCustom href={`/profile/${data?.Profile?.id}`}>
|
||||
Kunjungi Profile
|
||||
</ButtonCustom>
|
||||
</Grid.Col>
|
||||
@@ -35,9 +71,15 @@ export default function DonationInformationFunrising() {
|
||||
|
||||
<Spacing />
|
||||
|
||||
{Array.from({ length: 10 }).map((_, index) => (
|
||||
<Donation_BoxPublish key={index} id={index.toString()} />
|
||||
))}
|
||||
{loadList ? (
|
||||
<LoaderCustom />
|
||||
) : _.isEmpty(list) ? (
|
||||
<TextCustom align="center" color="gray" size="small">Belum ada data</TextCustom>
|
||||
) : (
|
||||
list?.map((item: any, index: number) => (
|
||||
<Donation_BoxPublish key={index} id={item?.id} data={item} />
|
||||
))
|
||||
)}
|
||||
</ViewWrapper>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -1,47 +1,8 @@
|
||||
import {
|
||||
BaseBox,
|
||||
Grid,
|
||||
StackCustom,
|
||||
TextCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import { MainColor } from "@/constants/color-palet";
|
||||
import { FontAwesome6 } from "@expo/vector-icons";
|
||||
import dayjs from "dayjs";
|
||||
import { useLocalSearchParams } from "expo-router";
|
||||
import Donation_ScreenListOfDonatur from "@/screens/Donation/ScreenListOfDonatur";
|
||||
|
||||
export default function Donation_ListOfDonatur() {
|
||||
return (
|
||||
<>
|
||||
<ViewWrapper>
|
||||
{Array.from({ length: 10 }).map((_, index) => (
|
||||
<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}>
|
||||
<StackCustom gap={"xs"}>
|
||||
<TextCustom bold size="large">
|
||||
Username
|
||||
</TextCustom>
|
||||
<TextCustom>Berdonas sebesar </TextCustom>
|
||||
<TextCustom bold size="large" color="yellow">
|
||||
Rp. 100.000
|
||||
</TextCustom>
|
||||
<TextCustom>{dayjs().format("DD MMM YYYY")}</TextCustom>
|
||||
</StackCustom>
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
</BaseBox>
|
||||
))}
|
||||
</ViewWrapper>
|
||||
</>
|
||||
);
|
||||
export default function DonationListOfDonatur() {
|
||||
const { id } = useLocalSearchParams();
|
||||
|
||||
return <Donation_ScreenListOfDonatur donationId={id as string} />;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
BoxButtonOnFooter,
|
||||
ButtonCenteredOnly,
|
||||
ButtonCustom,
|
||||
InformationBox,
|
||||
@@ -7,13 +9,127 @@ import {
|
||||
StackCustom,
|
||||
TextAreaCustom,
|
||||
TextInputCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import { router } from "expo-router";
|
||||
import NewWrapper from "@/components/_ShareComponent/NewWrapper";
|
||||
import DIRECTORY_ID from "@/constants/directory-id";
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
import {
|
||||
apiDonationCreate,
|
||||
apiDonationGetOne,
|
||||
} from "@/service/api-client/api-donation";
|
||||
import { uploadFileService } from "@/service/upload-service";
|
||||
import pickFile from "@/utils/pickFile";
|
||||
import { router, useLocalSearchParams } from "expo-router";
|
||||
import _ from "lodash";
|
||||
import { useEffect, useState } from "react";
|
||||
import Toast from "react-native-toast-message";
|
||||
|
||||
export default function DonationCreateStory() {
|
||||
const { user } = useAuth();
|
||||
const { id } = useLocalSearchParams();
|
||||
const [temporary, setTemporary] = useState<any>();
|
||||
const [data, setData] = useState({
|
||||
pembukaan: "",
|
||||
cerita: "",
|
||||
namaBank: "",
|
||||
rekening: "",
|
||||
});
|
||||
const [imageStory, setImageStory] = useState<string | null>(null);
|
||||
const [isLoading, setLoading] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
onLoadData();
|
||||
}, [id]);
|
||||
|
||||
const onLoadData = async () => {
|
||||
try {
|
||||
const response = await apiDonationGetOne({
|
||||
id: id as string,
|
||||
category: "temporary",
|
||||
});
|
||||
|
||||
setTemporary(response.data);
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
}
|
||||
};
|
||||
|
||||
const handlerSubmit = async () => {
|
||||
if (_.values(data).includes("")) {
|
||||
Toast.show({
|
||||
type: "error",
|
||||
text1: "Harap isi semua data",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
setLoading(true);
|
||||
const responseUploadImageDonasi = await uploadFileService({
|
||||
imageUri: imageStory,
|
||||
dirId: DIRECTORY_ID.donasi_cerita_image,
|
||||
});
|
||||
|
||||
const newData = {
|
||||
// Data Donasi
|
||||
temporaryId: temporary?.id,
|
||||
authorId: user?.id,
|
||||
title: temporary?.title,
|
||||
target: temporary?.target,
|
||||
donasiMaster_KategoriId: temporary?.donasiMaster_KategoriId,
|
||||
donasiMaster_DurasiId: temporary?.donasiMaster_DurasiId,
|
||||
imageId: temporary?.imageId,
|
||||
// Data Bank
|
||||
namaBank: data.namaBank,
|
||||
rekening: data.rekening,
|
||||
// Data Cerita
|
||||
imageCeritaId: responseUploadImageDonasi.data.id,
|
||||
pembukaan: data.pembukaan,
|
||||
cerita: data.cerita,
|
||||
};
|
||||
|
||||
const response = await apiDonationCreate({
|
||||
data: newData,
|
||||
category: "permanent",
|
||||
});
|
||||
|
||||
if (!response.success) {
|
||||
Toast.show({
|
||||
type: "error",
|
||||
text1: "Gagal membuat donasi",
|
||||
});
|
||||
}
|
||||
|
||||
Toast.show({
|
||||
type: "success",
|
||||
text1: "Donasi berhasil disimpan",
|
||||
});
|
||||
router.replace("/donation/status?status=review");
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
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
|
||||
@@ -22,6 +138,8 @@ export default function DonationCreateStory() {
|
||||
required
|
||||
showCount
|
||||
maxLength={1000}
|
||||
value={data.pembukaan}
|
||||
onChangeText={(value) => setData({ ...data, pembukaan: value })}
|
||||
/>
|
||||
<TextAreaCustom
|
||||
label="Tujuan Donasi"
|
||||
@@ -29,12 +147,19 @@ export default function DonationCreateStory() {
|
||||
required
|
||||
showCount
|
||||
maxLength={1000}
|
||||
value={data.cerita}
|
||||
onChangeText={(value) => setData({ ...data, cerita: value })}
|
||||
/>
|
||||
|
||||
<LandscapeFrameUploaded />
|
||||
<LandscapeFrameUploaded image={imageStory || ""} />
|
||||
<ButtonCenteredOnly
|
||||
onPress={() => {
|
||||
router.push("/(application)/(image)/take-picture/123");
|
||||
pickFile({
|
||||
allowedType: "image",
|
||||
setImageUri: ({ uri }) => {
|
||||
setImageStory(uri);
|
||||
},
|
||||
});
|
||||
}}
|
||||
icon="upload"
|
||||
>
|
||||
@@ -47,23 +172,19 @@ export default function DonationCreateStory() {
|
||||
label="Nama Bank"
|
||||
placeholder="Masukkan nama bank"
|
||||
required
|
||||
value={data.namaBank}
|
||||
onChangeText={(value) => setData({ ...data, namaBank: value })}
|
||||
/>
|
||||
<TextInputCustom
|
||||
label="Nomor Rekening"
|
||||
placeholder="Masukkan nomor rekening"
|
||||
required
|
||||
keyboardType="numeric"
|
||||
value={data.rekening}
|
||||
onChangeText={(value) => setData({ ...data, rekening: value })}
|
||||
/>
|
||||
|
||||
<Spacing />
|
||||
<ButtonCustom
|
||||
onPress={() => {
|
||||
router.replace(`/donation/(tabs)/status`);
|
||||
}}
|
||||
>
|
||||
Simpan
|
||||
</ButtonCustom>
|
||||
</StackCustom>
|
||||
<Spacing />
|
||||
</ViewWrapper>
|
||||
</NewWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,21 +1,149 @@
|
||||
import {
|
||||
BoxButtonOnFooter,
|
||||
ButtonCenteredOnly,
|
||||
ButtonCustom,
|
||||
InformationBox,
|
||||
LandscapeFrameUploaded,
|
||||
LoaderCustom,
|
||||
SelectCustom,
|
||||
Spacing,
|
||||
StackCustom,
|
||||
TextInputCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import { dummyDonasiDurasi } from "@/lib/dummy-data/donasi/durasi";
|
||||
import { dummyDonasiKategori } from "@/lib/dummy-data/donasi/kategori";
|
||||
import { router } from "expo-router";
|
||||
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";
|
||||
import { uploadFileService } from "@/service/upload-service";
|
||||
import { formatCurrencyDisplay } from "@/utils/formatCurrencyDisplay";
|
||||
import pickFile from "@/utils/pickFile";
|
||||
import { router, useFocusEffect } from "expo-router";
|
||||
import _ from "lodash";
|
||||
import { useCallback, useState } from "react";
|
||||
import Toast from "react-native-toast-message";
|
||||
|
||||
export default function DonationCreate() {
|
||||
const [listCategory, setListCategory] = useState<any[]>([]);
|
||||
const [listDuration, setListDuration] = useState<any[]>([]);
|
||||
const [loadList, setLoadList] = useState<boolean>(false);
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
const [image, setImage] = useState<string | null>(null);
|
||||
const [data, setData] = useState({
|
||||
kategoriId: "",
|
||||
title: "",
|
||||
target: "",
|
||||
durasiId: "",
|
||||
});
|
||||
|
||||
const displayTarget = formatCurrencyDisplay(data.target);
|
||||
const handleChangeCurrency = (field: keyof typeof data) => (text: string) => {
|
||||
const numeric = text.replace(/\D/g, "");
|
||||
setData((prev) => ({ ...prev, [field]: numeric }));
|
||||
};
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
onLoadList();
|
||||
}, []),
|
||||
);
|
||||
|
||||
const onLoadList = async () => {
|
||||
try {
|
||||
setLoadList(true);
|
||||
const response = await apiMasterDonation({ category: "" });
|
||||
|
||||
setListCategory(response.data.category);
|
||||
setListDuration(response.data.duration);
|
||||
} catch (error) {
|
||||
console.log(["ERROR"], error);
|
||||
setListCategory([]);
|
||||
setListDuration([]);
|
||||
} finally {
|
||||
setLoadList(false);
|
||||
}
|
||||
};
|
||||
|
||||
const validateData = () => {
|
||||
if (!data.title || !data.target || !data.durasiId || !data.kategoriId) {
|
||||
Toast.show({
|
||||
type: "error",
|
||||
text1: "Harap isi semua data",
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
const handlerSubmit = async () => {
|
||||
if (!validateData()) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
setIsLoading(true);
|
||||
const responseUploadImage = await uploadFileService({
|
||||
imageUri: image,
|
||||
dirId: DIRECTORY_ID.donasi_image,
|
||||
});
|
||||
|
||||
if (!responseUploadImage.success) {
|
||||
Toast.show({
|
||||
type: "error",
|
||||
text1: "Gagal mengunggah gambar",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const imageId = responseUploadImage.data.id;
|
||||
|
||||
const newData = {
|
||||
title: data.title,
|
||||
target: data.target,
|
||||
durasiId: data.durasiId,
|
||||
kategoriId: data.kategoriId,
|
||||
imageId: imageId,
|
||||
};
|
||||
|
||||
const response = await apiDonationCreate({
|
||||
data: newData,
|
||||
category: "temporary",
|
||||
});
|
||||
|
||||
if (!response.success) {
|
||||
Toast.show({
|
||||
type: "error",
|
||||
text1: "Gagal membuat donasi",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
router.push(`/donation/create-story?id=${response.data.id}`);
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
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." />
|
||||
|
||||
@@ -23,18 +151,28 @@ export default function DonationCreate() {
|
||||
label="Judul Donasi"
|
||||
placeholder="Masukkan Judul Donasi"
|
||||
required
|
||||
value={data.title}
|
||||
onChangeText={(value) => setData({ ...data, title: value })}
|
||||
/>
|
||||
<TextInputCustom
|
||||
iconLeft="Rp."
|
||||
label="Target Donasi"
|
||||
placeholder="Masukkan Target Donasi"
|
||||
required
|
||||
keyboardType="numeric"
|
||||
value={displayTarget}
|
||||
onChangeText={handleChangeCurrency("target")}
|
||||
/>
|
||||
|
||||
<LandscapeFrameUploaded />
|
||||
<LandscapeFrameUploaded image={image || ""} />
|
||||
<ButtonCenteredOnly
|
||||
onPress={() => {
|
||||
router.push("/(application)/(image)/take-picture/123");
|
||||
pickFile({
|
||||
allowedType: "image",
|
||||
setImageUri: ({ uri }) => {
|
||||
setImage(uri);
|
||||
},
|
||||
});
|
||||
}}
|
||||
icon="upload"
|
||||
>
|
||||
@@ -42,38 +180,47 @@ export default function DonationCreate() {
|
||||
</ButtonCenteredOnly>
|
||||
<Spacing />
|
||||
|
||||
<SelectCustom
|
||||
data={dummyDonasiKategori.map((item) => ({
|
||||
label: item.label,
|
||||
value: item.value,
|
||||
}))}
|
||||
onChange={(value) => console.log(value)}
|
||||
label="Pilih Kategori Donasi"
|
||||
placeholder="Pilih Kategori Donasi"
|
||||
required
|
||||
/>
|
||||
{loadList ? (
|
||||
<LoaderCustom />
|
||||
) : (
|
||||
<SelectCustom
|
||||
data={
|
||||
_.isEmpty(listCategory)
|
||||
? []
|
||||
: listCategory?.map((item: any) => ({
|
||||
label: item.name,
|
||||
value: item.id,
|
||||
}))
|
||||
}
|
||||
label="Pilih Kategori Donasi"
|
||||
placeholder="Pilih Kategori Donasi"
|
||||
required
|
||||
value={data.kategoriId}
|
||||
onChange={(value: any) => setData({ ...data, kategoriId: value })}
|
||||
/>
|
||||
)}
|
||||
|
||||
<SelectCustom
|
||||
data={dummyDonasiDurasi.map((item) => ({
|
||||
label: item.label,
|
||||
value: item.value,
|
||||
}))}
|
||||
onChange={(value) => console.log(value)}
|
||||
label="Pilih Durasi Donasi"
|
||||
placeholder="Pilih Durasi Donasi"
|
||||
required
|
||||
/>
|
||||
<Spacing />
|
||||
<ButtonCustom
|
||||
onPress={() => {
|
||||
router.replace("/donation/create-story");
|
||||
}}
|
||||
>
|
||||
Selanjutnya
|
||||
</ButtonCustom>
|
||||
<Spacing />
|
||||
{loadList ? (
|
||||
<LoaderCustom />
|
||||
) : (
|
||||
<SelectCustom
|
||||
data={
|
||||
_.isEmpty(listDuration)
|
||||
? []
|
||||
: listDuration?.map((item: any) => ({
|
||||
label: item.name + `${" hari"}`,
|
||||
value: item.id,
|
||||
}))
|
||||
}
|
||||
label="Pilih Durasi Donasi"
|
||||
placeholder="Pilih Durasi Donasi"
|
||||
required
|
||||
value={data.durasiId}
|
||||
onChange={(value: any) => setData({ ...data, durasiId: value })}
|
||||
/>
|
||||
)}
|
||||
</StackCustom>
|
||||
<Spacing />
|
||||
</ViewWrapper>
|
||||
</NewWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -4,10 +4,34 @@ import {
|
||||
IconHome,
|
||||
IconStatus,
|
||||
} from "@/components/_Icon";
|
||||
import BackButtonFromNotification from "@/components/Button/BackButtonFromNotification";
|
||||
import { TabsStyles } from "@/styles/tabs-styles";
|
||||
import { Tabs } from "expo-router";
|
||||
import { router, Tabs, useLocalSearchParams, useNavigation } from "expo-router";
|
||||
import { useLayoutEffect } from "react";
|
||||
|
||||
export default function EventTabsLayout() {
|
||||
const navigation = useNavigation();
|
||||
|
||||
const { from, category } = useLocalSearchParams<{
|
||||
from?: string;
|
||||
category?: string;
|
||||
}>();
|
||||
|
||||
console.log("from", from);
|
||||
console.log("category", category);
|
||||
|
||||
// Atur header secara dinamis
|
||||
useLayoutEffect(() => {
|
||||
navigation.setOptions({
|
||||
headerLeft: () => (
|
||||
<BackButtonFromNotification
|
||||
from={from as string}
|
||||
category={category as string}
|
||||
/>
|
||||
),
|
||||
});
|
||||
}, [from, router, navigation]);
|
||||
|
||||
return (
|
||||
<Tabs screenOptions={TabsStyles}>
|
||||
<Tabs.Screen
|
||||
|
||||
@@ -1,43 +1,10 @@
|
||||
import {
|
||||
AvatarCustom,
|
||||
AvatarUsernameAndOtherComponent,
|
||||
BoxWithHeaderSection,
|
||||
Grid,
|
||||
StackCustom,
|
||||
TextCustom,
|
||||
ViewWrapper
|
||||
} from "@/components";
|
||||
import React from "react";
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import Event_ScreenContribution from "@/screens/Event/ScreenContribution";
|
||||
|
||||
export default function EventContribution() {
|
||||
return (
|
||||
<ViewWrapper hideFooter>
|
||||
{Array.from({ length: 10 }).map((_, index) => (
|
||||
<BoxWithHeaderSection key={index} href={`/event/${index}/contribution`}>
|
||||
<StackCustom>
|
||||
<AvatarUsernameAndOtherComponent
|
||||
avatarHref={`/profile/${index}`}
|
||||
rightComponent={
|
||||
<TextCustom truncate>
|
||||
{new Date().toLocaleDateString()}
|
||||
</TextCustom>
|
||||
}
|
||||
/>
|
||||
|
||||
<TextCustom bold align="center" size="xlarge">
|
||||
Judul Event Disini
|
||||
</TextCustom>
|
||||
|
||||
<Grid>
|
||||
{Array.from({ length: 4 }).map((_, index2) => (
|
||||
<Grid.Col span={3} key={index2}>
|
||||
<AvatarCustom size="sm" href={`/profile/${index2}`} />
|
||||
</Grid.Col>
|
||||
))}
|
||||
</Grid>
|
||||
</StackCustom>
|
||||
</BoxWithHeaderSection>
|
||||
))}
|
||||
</ViewWrapper>
|
||||
<>
|
||||
<Event_ScreenContribution />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,68 +1,10 @@
|
||||
import { ButtonCustom, Spacing, TextCustom } from "@/components";
|
||||
import ViewWrapper from "@/components/_ShareComponent/ViewWrapper";
|
||||
import { AccentColor, MainColor } from "@/constants/color-palet";
|
||||
import Event_BoxPublishSection from "@/screens/Event/BoxPublishSection";
|
||||
import { useState } from "react";
|
||||
import { View } from "react-native";
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import Event_ScreenHistory from "@/screens/Event/ScreenHistory";
|
||||
|
||||
export default function EventHistory() {
|
||||
const [activeCategory, setActiveCategory] = useState<string | null>("all");
|
||||
|
||||
const handlePress = (item: any) => {
|
||||
setActiveCategory(item);
|
||||
// tambahkan logika lain seperti filter dsb.
|
||||
};
|
||||
|
||||
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 (
|
||||
<ViewWrapper headerComponent={headerComponent} hideFooter>
|
||||
{Array.from({ length: 10 }).map((_, index) => (
|
||||
<Event_BoxPublishSection
|
||||
key={index.toString()}
|
||||
id={index.toString()}
|
||||
username={`Riwayat ${activeCategory === "main" ? "Saya" : "Semua"}`}
|
||||
rightComponentAvatar={
|
||||
<TextCustom>{new Date().toLocaleDateString()}</TextCustom>
|
||||
}
|
||||
href={`/event/${index}/history`}
|
||||
/>
|
||||
))}
|
||||
</ViewWrapper>
|
||||
<>
|
||||
<Event_ScreenHistory />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,23 +1,9 @@
|
||||
import ViewWrapper from "@/components/_ShareComponent/ViewWrapper";
|
||||
import FloatingButton from "@/components/Button/FloatingButton";
|
||||
import Event_BoxPublishSection from "@/screens/Event/BoxPublishSection";
|
||||
import { router } from "expo-router";
|
||||
import Event_ScreenBeranda from "@/screens/Event/ScreenBeranda";
|
||||
|
||||
export default function EventBeranda() {
|
||||
return (
|
||||
<ViewWrapper
|
||||
hideFooter
|
||||
floatingButton={
|
||||
<FloatingButton onPress={() => router.push("/event/create")} />
|
||||
}
|
||||
>
|
||||
{Array.from({ length: 10 }).map((_, index) => (
|
||||
<Event_BoxPublishSection
|
||||
key={index}
|
||||
id={index.toString()}
|
||||
href={`/event/${index}/publish`}
|
||||
/>
|
||||
))}
|
||||
</ViewWrapper>
|
||||
<>
|
||||
<Event_ScreenBeranda />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,63 +1,10 @@
|
||||
import {
|
||||
BoxWithHeaderSection,
|
||||
Grid,
|
||||
ScrollableCustom,
|
||||
StackCustom,
|
||||
TextCustom
|
||||
} from "@/components";
|
||||
import ViewWrapper from "@/components/_ShareComponent/ViewWrapper";
|
||||
import { dummyMasterStatus } from "@/lib/dummy-data/_master/status";
|
||||
import { useState } from "react";
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import Event_ScreenStatus from "@/screens/Event/ScreenStatus";
|
||||
|
||||
export default function EventStatus() {
|
||||
const id = "test-id-event";
|
||||
|
||||
const [activeCategory, setActiveCategory] = useState<string | null>(
|
||||
"publish"
|
||||
);
|
||||
|
||||
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}>
|
||||
<BoxWithHeaderSection href={`/event/${id}/${activeCategory}/detail-event`}>
|
||||
<StackCustom gap={"xs"}>
|
||||
<Grid>
|
||||
<Grid.Col span={8}>
|
||||
<TextCustom truncate bold>
|
||||
Lorem ipsum,{" "}
|
||||
<TextCustom color="green">{activeCategory}</TextCustom> dolor
|
||||
sit amet consectetur adipisicing elit.
|
||||
</TextCustom>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={4} style={{ alignItems: "flex-end" }}>
|
||||
<TextCustom>{new Date().toLocaleDateString()}</TextCustom>
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
|
||||
<TextCustom truncate={2}>
|
||||
Lorem ipsum dolor sit amet consectetur adipisicing elit. Consectetur
|
||||
eveniet ab eum ducimus tempore a quia deserunt quisquam. Tempora,
|
||||
atque. Aperiam minima asperiores dicta perferendis quis adipisci,
|
||||
dolore optio porro!
|
||||
</TextCustom>
|
||||
</StackCustom>
|
||||
</BoxWithHeaderSection>
|
||||
</ViewWrapper>
|
||||
<>
|
||||
<Event_ScreenStatus />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
BaseBox,
|
||||
DotButton,
|
||||
@@ -11,17 +12,64 @@ import {
|
||||
} from "@/components";
|
||||
import { IMenuDrawerItem } from "@/components/_Interface/types";
|
||||
import LeftButtonCustom from "@/components/Button/BackButton";
|
||||
import Event_AlertButtonStatusSection from "@/screens/Event/AlertButtonStatusSection";
|
||||
import Event_ButtonStatusSection from "@/screens/Event/ButtonStatusSection";
|
||||
import { menuDrawerDraftEvent } from "@/screens/Event/menuDrawerDraft";
|
||||
import { router, Stack, useLocalSearchParams } from "expo-router";
|
||||
import { useState } from "react";
|
||||
import { apiEventGetOne } from "@/service/api-client/api-event";
|
||||
import { dateTimeView } from "@/utils/dateTimeView";
|
||||
import {
|
||||
router,
|
||||
Stack,
|
||||
useFocusEffect,
|
||||
useLocalSearchParams,
|
||||
} from "expo-router";
|
||||
import { useCallback, useState } from "react";
|
||||
|
||||
export default function EventDetailStatus() {
|
||||
const { id, status } = useLocalSearchParams();
|
||||
const [openDrawer, setOpenDrawer] = useState(false);
|
||||
const [openAlert, setOpenAlert] = useState(false);
|
||||
const [openDeleteAlert, setOpenDeleteAlert] = useState(false);
|
||||
// const [openAlert, setOpenAlert] = useState(false);
|
||||
|
||||
const [data, setData] = useState<any>();
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
onLoadData();
|
||||
}, [id])
|
||||
);
|
||||
|
||||
async function onLoadData() {
|
||||
try {
|
||||
const response = await apiEventGetOne({ id: id as string });
|
||||
if (response.success) {
|
||||
setData(response.data);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
}
|
||||
}
|
||||
|
||||
const listData = [
|
||||
{
|
||||
title: "Lokasi",
|
||||
value: data?.lokasi || "-",
|
||||
},
|
||||
{
|
||||
title: "Tipe Acara",
|
||||
value: data?.EventMaster_TipeAcara?.name || "-",
|
||||
},
|
||||
{
|
||||
title: "Tanggal Mulai",
|
||||
value: dateTimeView({ date: data?.tanggal }) || "-",
|
||||
},
|
||||
{
|
||||
title: "Tanggal Berakhir",
|
||||
value: dateTimeView({ date: data?.tanggalSelesai }) || "-",
|
||||
},
|
||||
{
|
||||
title: "Deskripsi",
|
||||
value: data?.deskripsi || "-",
|
||||
},
|
||||
];
|
||||
|
||||
const handlePress = (item: IMenuDrawerItem) => {
|
||||
console.log("PATH >> ", item.path);
|
||||
@@ -45,7 +93,7 @@ export default function EventDetailStatus() {
|
||||
<BaseBox>
|
||||
<StackCustom>
|
||||
<TextCustom bold align="center" size="xlarge">
|
||||
Judul event {status}
|
||||
{data?.title || "-"}
|
||||
</TextCustom>
|
||||
{listData.map((item, index) => (
|
||||
<Grid key={index}>
|
||||
@@ -60,9 +108,8 @@ export default function EventDetailStatus() {
|
||||
</StackCustom>
|
||||
</BaseBox>
|
||||
<Event_ButtonStatusSection
|
||||
id={id as string}
|
||||
status={status as string}
|
||||
onOpenAlert={setOpenAlert}
|
||||
onOpenDeleteAlert={setOpenDeleteAlert}
|
||||
/>
|
||||
<Spacing />
|
||||
</ViewWrapper>
|
||||
@@ -70,7 +117,7 @@ export default function EventDetailStatus() {
|
||||
<DrawerCustom
|
||||
isVisible={openDrawer}
|
||||
closeDrawer={() => setOpenDrawer(false)}
|
||||
height={250}
|
||||
height={"auto"}
|
||||
>
|
||||
<MenuDrawerDynamicGrid
|
||||
data={menuDrawerDraftEvent({ id: id as string }) as any}
|
||||
@@ -78,40 +125,6 @@ export default function EventDetailStatus() {
|
||||
onPressItem={handlePress as any}
|
||||
/>
|
||||
</DrawerCustom>
|
||||
|
||||
<Event_AlertButtonStatusSection
|
||||
id={id as string}
|
||||
status={status as string}
|
||||
openAlert={openAlert}
|
||||
setOpenAlert={setOpenAlert}
|
||||
openDeleteAlert={openDeleteAlert}
|
||||
setOpenDeleteAlert={setOpenDeleteAlert}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const listData = [
|
||||
{
|
||||
title: "Lokasi",
|
||||
value:
|
||||
"Lorem ipsum dolor sit amet consectetur adipisicing elit. Consectetur eveniet ab eum ducimus tempore a quia deserunt quisquam. Tempora, atque. Aperiam minima asperiores dicta perferendis quis adipisci, dolore optio porro!",
|
||||
},
|
||||
{
|
||||
title: "Tipe Acara",
|
||||
value: "Workshop",
|
||||
},
|
||||
{
|
||||
title: "Tanggal Mulai",
|
||||
value: "Senin, 18 Juli 2025, 10:00 WIB",
|
||||
},
|
||||
{
|
||||
title: "Tanggal Berakhir",
|
||||
value: "Selasa, 19 Juli 2025, 12:00 WIB",
|
||||
},
|
||||
{
|
||||
title: "Deskripsi",
|
||||
value:
|
||||
"Lorem ipsum dolor sit amet consectetur adipisicing elit. Consectetur eveniet ab eum ducimus tempore a quia deserunt quisquam. Tempora, atque. Aperiam minima asperiores dicta perferendis quis adipisci, dolore optio porro!",
|
||||
},
|
||||
];
|
||||
|
||||
554
app/(application)/(user)/event/[id]/confirmation.tsx
Normal file
@@ -0,0 +1,554 @@
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
BaseBox,
|
||||
ButtonCustom,
|
||||
CenterCustom,
|
||||
LoaderCustom,
|
||||
StackCustom,
|
||||
TextCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import { AccentColor, MainColor } from "@/constants/color-palet";
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
import {
|
||||
apiEventConfirmationAction,
|
||||
apiEventGetConfirmation,
|
||||
apiEventJoin,
|
||||
} from "@/service/api-client/api-event";
|
||||
import { Ionicons } from "@expo/vector-icons";
|
||||
import dayjs from "dayjs";
|
||||
import isBetween from "dayjs/plugin/isBetween";
|
||||
import {
|
||||
Redirect,
|
||||
router,
|
||||
Stack,
|
||||
useFocusEffect,
|
||||
useLocalSearchParams,
|
||||
} from "expo-router";
|
||||
import React, { useCallback, useState } from "react";
|
||||
import { View } from "react-native";
|
||||
import Toast from "react-native-toast-message";
|
||||
|
||||
// Extend Day.js dengan plugin isBetween
|
||||
dayjs.extend(isBetween);
|
||||
|
||||
interface DataEvent {
|
||||
id: string;
|
||||
title: string;
|
||||
tanggal: Date;
|
||||
tanggalSelesai: Date;
|
||||
lokasi: string;
|
||||
Author: {
|
||||
id: string;
|
||||
username: string;
|
||||
Profile: {
|
||||
id: string;
|
||||
name: string;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export default function UserEventConfirmation() {
|
||||
const { token } = useAuth();
|
||||
const { id, userId: authorId } = useLocalSearchParams();
|
||||
const { user } = useAuth();
|
||||
const [data, setData] = useState<DataEvent | null>(null);
|
||||
const [peserta, setPeserta] = useState<boolean | null>(null);
|
||||
const [konfirmasi, setKonfirmasi] = useState<boolean | null>(null);
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
checkTokenAndDataParticipants() || console.log("Token is null");
|
||||
}, [token, id, user?.id])
|
||||
);
|
||||
|
||||
const checkTokenAndDataParticipants = async () => {
|
||||
if (!token) {
|
||||
return <Redirect href={`/`} />;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await apiEventGetConfirmation({
|
||||
id: id as string,
|
||||
userId: user?.id as string,
|
||||
});
|
||||
|
||||
if (response.success) {
|
||||
setData(response.data?.dataEvent);
|
||||
setPeserta(response.data?.peserta);
|
||||
setKonfirmasi(response.data?.kehadiran);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("[ERROR CONFIRMATION]", error);
|
||||
}
|
||||
};
|
||||
|
||||
const handlerReturn = () => {
|
||||
const now = dayjs(); // asumsi: UTC, sesuai dengan API
|
||||
|
||||
// --- [1] Loading & Data tidak ditemukan ---
|
||||
if (data === undefined || data === null) {
|
||||
if (peserta === null && konfirmasi === null) {
|
||||
return <LoaderCustom />;
|
||||
}
|
||||
return (
|
||||
<BaseBox>
|
||||
<TextCustom bold align="center" size={"large"}>
|
||||
Data Tidak Ditemukan
|
||||
</TextCustom>
|
||||
<BackToOtherPath path="home" />
|
||||
</BaseBox>
|
||||
);
|
||||
}
|
||||
|
||||
// --- [2] Ambil waktu event dari `data` ---
|
||||
const eventStart = dayjs(data.tanggal);
|
||||
const eventEnd = dayjs(data.tanggalSelesai);
|
||||
|
||||
// --- [3] Definisikan jendela konfirmasi: 1 jam sebelum mulai → 1 jam setelah selesai ---
|
||||
const confirmationStart = eventStart.subtract(1, "hour");
|
||||
const confirmationEnd = eventEnd.add(1, "hour");
|
||||
const isWithinConfirmationWindow = now.isBetween(
|
||||
confirmationStart,
|
||||
confirmationEnd,
|
||||
null,
|
||||
"[]"
|
||||
);
|
||||
|
||||
// --- [4] Status waktu event (untuk pesan UI) ---
|
||||
const isBeforeEvent = now.isBefore(eventStart);
|
||||
const isAfterEvent = now.isAfter(eventEnd);
|
||||
const isDuringEvent = !isBeforeEvent && !isAfterEvent;
|
||||
|
||||
// --- [5] Handle berdasarkan waktu dan status peserta/konfirmasi ---
|
||||
|
||||
// 🟢 Acara sudah selesai
|
||||
if (isAfterEvent) {
|
||||
if (peserta === false) {
|
||||
return (
|
||||
<TamplateBox data={data}>
|
||||
<TamplateText text="Event telah selesai, sehingga konfirmasi kehadiran sudah tidak dapat dilakukan. Terima kasih atas perhatian dan minat Anda. Kami berharap dapat bertemu di acara kami berikutnya." />
|
||||
<BackToOtherPath
|
||||
path="event"
|
||||
id={data.id}
|
||||
isAfterEvent={isAfterEvent}
|
||||
/>
|
||||
</TamplateBox>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<TamplateBox data={data}>
|
||||
<TamplateText
|
||||
text={`Event telah selesai, anda terdaftar sebagai peserta dan ${
|
||||
konfirmasi
|
||||
? "Anda telah mengonfirmasi kehadiran."
|
||||
: "Anda tidak mengonfirmasi kehadiran."
|
||||
} Terima kasih atas perhatian dan minat Anda. Kami berharap dapat bertemu di acara kami berikutnya.`}
|
||||
/>
|
||||
<BackToOtherPath
|
||||
path="event"
|
||||
id={data.id}
|
||||
isAfterEvent={isAfterEvent}
|
||||
/>
|
||||
</TamplateBox>
|
||||
);
|
||||
}
|
||||
|
||||
// 🔵 Acara belum mulai & belum terdaftar
|
||||
if (isBeforeEvent) {
|
||||
if (peserta === false) {
|
||||
return (
|
||||
<NotStarted_And_UserNotParticipan
|
||||
id={data.id}
|
||||
userId={user?.id as string}
|
||||
data={data}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
// Peserta sudah daftar → cek apakah sudah boleh konfirmasi
|
||||
if (isWithinConfirmationWindow && peserta === true) {
|
||||
if (konfirmasi === false) {
|
||||
return (
|
||||
<UserParticipan_And_DuringEvent
|
||||
id={data.id}
|
||||
userId={user?.id as string}
|
||||
data={data}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<TamplateBox data={data}>
|
||||
<TamplateText text="Terimakasih telah mengonfirmasi kehadiran. Silahkan lihat peserta lain pada halaman event atau kembali ke halaman home. Selamat menikmati acara dan selamat berpartisipasi." />
|
||||
<BackToOtherPath
|
||||
path="event"
|
||||
id={data.id}
|
||||
isAfterEvent={isAfterEvent}
|
||||
/>
|
||||
</TamplateBox>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<TamplateBox data={data}>
|
||||
<TamplateText text="Anda telah terdaftar sebagai peserta pada Event ini. Konfirmasi kehadiran dibuka 1 jam sebelum acara dimulai." />
|
||||
<BackToOtherPath
|
||||
path="event"
|
||||
id={data.id}
|
||||
isAfterEvent={isAfterEvent}
|
||||
/>
|
||||
</TamplateBox>
|
||||
);
|
||||
}
|
||||
|
||||
// 🟡 Acara sedang berlangsung & belum terdaftar
|
||||
if (isDuringEvent) {
|
||||
if (peserta === false) {
|
||||
return (
|
||||
<UserNotParticipan_And_DuringEvent
|
||||
id={data.id}
|
||||
userId={user?.id as string}
|
||||
data={data}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (peserta === true) {
|
||||
if (isWithinConfirmationWindow) {
|
||||
if (konfirmasi === false) {
|
||||
return (
|
||||
<TamplateBox data={data}>
|
||||
<TamplateText text="Konfirmasi Kehadiran" />
|
||||
</TamplateBox>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<TamplateBox data={data}>
|
||||
<TamplateText text="Anda telah mengonfirmasi kehadiran dalam event ini. Terima kasih dan selamat menikmati acara. Have fun!" />
|
||||
<BackToOtherPath
|
||||
path="event"
|
||||
id={data.id}
|
||||
isAfterEvent={isAfterEvent}
|
||||
/>
|
||||
</TamplateBox>
|
||||
);
|
||||
}
|
||||
|
||||
// Ini sangat jarang terjadi selama event berlangsung, tapi aman
|
||||
return (
|
||||
<TamplateBox data={data}>
|
||||
<TamplateText text="Konfirmasi kehadiran tidak tersedia saat ini." />
|
||||
<BackToOtherPath path="home" />
|
||||
</TamplateBox>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// 🛑 Fallback aman
|
||||
return (
|
||||
<BaseBox>
|
||||
<StackCustom>
|
||||
<TamplateText text="Terjadi kesalahan tak terduga pada logika waktu." />
|
||||
<BackToOtherPath path="home" />
|
||||
</StackCustom>
|
||||
</BaseBox>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Stack.Screen
|
||||
options={{
|
||||
title: "Konfirmasi Event",
|
||||
// headerLeft: () => (
|
||||
// <Ionicons
|
||||
// name="arrow-back"
|
||||
// size={20}
|
||||
// color={MainColor.yellow}
|
||||
// onPress={() =>
|
||||
// router.navigate("/(application)/(user)/event/create")
|
||||
// }
|
||||
// />
|
||||
// ),
|
||||
}}
|
||||
/>
|
||||
<ViewWrapper>{handlerReturn()}</ViewWrapper>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const TamplateBox = ({
|
||||
data,
|
||||
children,
|
||||
}: {
|
||||
data: DataEvent;
|
||||
children: React.ReactNode;
|
||||
}) => {
|
||||
return (
|
||||
<>
|
||||
<CenterCustom>
|
||||
<BaseBox>
|
||||
<StackCustom gap={"lg"}>
|
||||
<StackCustom gap={"sm"}>
|
||||
<TextCustom bold align="center" size={"large"}>
|
||||
{data?.title}
|
||||
</TextCustom>
|
||||
<View
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
justifyContent: "center",
|
||||
// backgroundColor: AccentColor.blue,
|
||||
// borderColor: AccentColor.blue,
|
||||
// borderWidth: 1,
|
||||
// borderRadius: 4,
|
||||
// padding: 3
|
||||
}}
|
||||
>
|
||||
<TextCustom align="center" size="small" bold>
|
||||
{dayjs(data?.tanggal).format("DD MMM YYYY: HH:mm")}
|
||||
</TextCustom>
|
||||
<TextCustom align="center" size="small" bold>
|
||||
{" "}
|
||||
-{" "}
|
||||
</TextCustom>
|
||||
<TextCustom align="center" size="small" bold>
|
||||
{dayjs(data?.tanggalSelesai).format("DD MMM YYYY: HH:mm")}
|
||||
</TextCustom>
|
||||
</View>
|
||||
</StackCustom>
|
||||
|
||||
{children}
|
||||
</StackCustom>
|
||||
</BaseBox>
|
||||
</CenterCustom>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const TamplateText = ({ text }: { text: React.ReactNode }) => {
|
||||
return (
|
||||
<>
|
||||
<TextCustom align="center">{text}</TextCustom>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
type BackToOtherPathProps =
|
||||
| { path: "home" | "beranda-event"; id?: never; isAfterEvent?: never }
|
||||
| { path: "event"; id: string; isAfterEvent: boolean };
|
||||
|
||||
const BackToOtherPath = ({ path, id, isAfterEvent }: BackToOtherPathProps) => {
|
||||
return (
|
||||
<>
|
||||
{path === "home" ? (
|
||||
<ButtonCustom
|
||||
onPress={() => {
|
||||
router.replace("/(application)/home");
|
||||
}}
|
||||
>
|
||||
Home
|
||||
</ButtonCustom>
|
||||
) : (
|
||||
<View
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
gap: 10,
|
||||
|
||||
justifyContent: "center",
|
||||
}}
|
||||
>
|
||||
<ButtonCustom
|
||||
onPress={() => {
|
||||
router.replace("/(application)/home");
|
||||
}}
|
||||
>
|
||||
Home
|
||||
</ButtonCustom>
|
||||
<ButtonCustom
|
||||
onPress={() => {
|
||||
if (path === "event") {
|
||||
if (isAfterEvent) {
|
||||
router.push(`/(application)/(user)/event/${id}/history`);
|
||||
} else {
|
||||
router.push(`/(application)/(user)/event/${id}/publish`);
|
||||
}
|
||||
} else if (path === "beranda-event") {
|
||||
router.push(`/(application)/(user)/event`);
|
||||
} else {
|
||||
console.log("[PATH]", path);
|
||||
}
|
||||
}}
|
||||
>
|
||||
Lihat {path === "event" ? "Event" : "Beranda Event"}
|
||||
</ButtonCustom>
|
||||
</View>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
// 🔵 Acara belum mulai & belum terdaftar
|
||||
const NotStarted_And_UserNotParticipan = ({
|
||||
id,
|
||||
userId,
|
||||
data,
|
||||
}: {
|
||||
id: string;
|
||||
userId: string;
|
||||
data: DataEvent;
|
||||
}) => {
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
|
||||
const handlerJoinEvent = async () => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
|
||||
const response = await apiEventJoin({
|
||||
id: id as string,
|
||||
userId: userId as string,
|
||||
});
|
||||
|
||||
if (!response.success) {
|
||||
Toast.show({
|
||||
type: "error",
|
||||
text1: "Anda gagal join",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
Toast.show({
|
||||
type: "success",
|
||||
text1: "Anda berhasil join",
|
||||
});
|
||||
router.navigate(`/(application)/(user)/event/${id}/publish`);
|
||||
} catch (error) {
|
||||
console.log("[ERROR JOIN EVENT]", error);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<TamplateBox data={data}>
|
||||
<TamplateText text="Anda belum terdaftar sebagai peserta & Event belum dimulai. Silahkan daftarkan diri anda terlebih dahulu" />
|
||||
<ButtonCustom onPress={handlerJoinEvent} isLoading={isLoading}>
|
||||
Join
|
||||
</ButtonCustom>
|
||||
</TamplateBox>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
// 🟡 ZONA ACARA BERLANGSUNG
|
||||
// Acara sedang berlangsung & belum terdaftar & user harus join dan konfirmasi
|
||||
const UserNotParticipan_And_DuringEvent = ({
|
||||
id,
|
||||
userId,
|
||||
data,
|
||||
}: {
|
||||
id: string;
|
||||
userId: string;
|
||||
data: DataEvent;
|
||||
}) => {
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
|
||||
const handlerSubmit = async () => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
|
||||
const response = await apiEventConfirmationAction({
|
||||
id: id as string,
|
||||
userId: userId as string,
|
||||
category: "join_and_confirm",
|
||||
});
|
||||
|
||||
if (!response.success) {
|
||||
Toast.show({
|
||||
type: "error",
|
||||
text1: "Anda gagal join & konfirmasi",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
Toast.show({
|
||||
type: "success",
|
||||
text1: "Anda berhasil join & konfirmasi",
|
||||
});
|
||||
router.navigate(`/(application)/(user)/event/${id}/publish`);
|
||||
} catch (error) {
|
||||
console.log("[ERROR JOIN & CONFIRMATION EVENT]", error);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<TamplateBox data={data}>
|
||||
<TamplateText text="Anda belum terdaftar sebagai peserta & Event sedang berlangsung. Silahkan daftarkan diri anda & Konfirmasi kehadiran" />
|
||||
|
||||
<ButtonCustom onPress={() => handlerSubmit()} isLoading={isLoading}>
|
||||
Join & Konfirmasi
|
||||
</ButtonCustom>
|
||||
</TamplateBox>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
// 🟡 ZONA ACARA BERLANGSUN
|
||||
// User sudah terdaftar & Event sedang berlangsung & user harus konfirmasi
|
||||
const UserParticipan_And_DuringEvent = ({
|
||||
id,
|
||||
userId,
|
||||
data,
|
||||
}: {
|
||||
id: string;
|
||||
userId: string;
|
||||
data: DataEvent;
|
||||
}) => {
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
|
||||
const handlerSubmit = async () => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
|
||||
const response = await apiEventConfirmationAction({
|
||||
id: id as string,
|
||||
userId: userId as string,
|
||||
category: "confirmation",
|
||||
});
|
||||
|
||||
if (!response.success) {
|
||||
Toast.show({
|
||||
type: "error",
|
||||
text1: "Anda gagal konfirmasi",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
Toast.show({
|
||||
type: "success",
|
||||
text1: "Anda berhasil konfirmasi",
|
||||
});
|
||||
router.navigate(`/(application)/(user)/event/${id}/publish`);
|
||||
} catch (error) {
|
||||
console.log("[ERROR JOIN & CONFIRMATION EVENT]", error);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<TamplateBox data={data}>
|
||||
<TamplateText text="Anda sudah terdaftar sebagai peserta & Event sedang berlangsung. Silahkan konfirmasi kehadiran" />
|
||||
|
||||
<ButtonCustom onPress={() => handlerSubmit()} isLoading={isLoading}>
|
||||
Konfirmasi
|
||||
</ButtonCustom>
|
||||
</TamplateBox>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -1,20 +1,43 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
DotButton,
|
||||
DrawerCustom,
|
||||
LoaderCustom,
|
||||
MenuDrawerDynamicGrid,
|
||||
ViewWrapper,
|
||||
Spacing,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import { IMenuDrawerItem } from "@/components/_Interface/types";
|
||||
import LeftButtonCustom from "@/components/Button/BackButton";
|
||||
import Event_BoxDetailPublishSection from "@/screens/Event/BoxDetailPublishSection";
|
||||
import { menuDrawerPublishEvent } from "@/screens/Event/menuDrawerPublish";
|
||||
import { apiEventGetOne } from "@/service/api-client/api-event";
|
||||
import { router, Stack, useLocalSearchParams } from "expo-router";
|
||||
import { useState } from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
export default function EventDetailContribution() {
|
||||
const { id } = useLocalSearchParams();
|
||||
const [openDrawer, setOpenDrawer] = useState(false);
|
||||
const [data, setData] = useState<any>();
|
||||
const [isLoadData, setIsLoadData] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
onLoadData();
|
||||
}, [id]);
|
||||
|
||||
const onLoadData = async () => {
|
||||
try {
|
||||
setIsLoadData(true);
|
||||
const response = await apiEventGetOne({ id: id as string });
|
||||
if (response.success) {
|
||||
setData(response.data);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
} finally {
|
||||
setIsLoadData(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handlePress = (item: IMenuDrawerItem) => {
|
||||
console.log("PATH ", item.path);
|
||||
@@ -32,18 +55,22 @@ export default function EventDetailContribution() {
|
||||
}}
|
||||
/>
|
||||
<ViewWrapper>
|
||||
<Event_BoxDetailPublishSection />
|
||||
{isLoadData ? (
|
||||
<LoaderCustom />
|
||||
) : (
|
||||
<Event_BoxDetailPublishSection data={data} />
|
||||
)}
|
||||
<Spacing />
|
||||
</ViewWrapper>
|
||||
<DrawerCustom
|
||||
isVisible={openDrawer}
|
||||
closeDrawer={() => setOpenDrawer(false)}
|
||||
height={250}
|
||||
height={"auto"}
|
||||
>
|
||||
<MenuDrawerDynamicGrid
|
||||
data={menuDrawerPublishEvent({ id: id as string })}
|
||||
columns={4}
|
||||
onPressItem={handlePress}
|
||||
onPressItem={handlePress as any}
|
||||
/>
|
||||
</DrawerCustom>
|
||||
</>
|
||||
|
||||
@@ -1,107 +1,289 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
BoxButtonOnFooter,
|
||||
ButtonCustom,
|
||||
LoaderCustom,
|
||||
NewWrapper,
|
||||
SelectCustom,
|
||||
Spacing,
|
||||
StackCustom,
|
||||
TextAreaCustom,
|
||||
TextCustom,
|
||||
TextInputCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import ListSkeletonComponent from "@/components/_ShareComponent/ListSkeletonComponent";
|
||||
import DateTimePickerCustom from "@/components/DateInput/DateTimePickerCustom";
|
||||
import { masterTypeEvent } from "@/lib/dummy-data/event/master-type-event";
|
||||
import {
|
||||
apiEventGetOne,
|
||||
apiEventUpdateData,
|
||||
} from "@/service/api-client/api-event";
|
||||
import { apiMasterEventType } from "@/service/api-client/api-master";
|
||||
import { DateTimePickerEvent } from "@react-native-community/datetimepicker";
|
||||
import { router } from "expo-router";
|
||||
import React, { useState } from "react";
|
||||
import { Platform } from "react-native";
|
||||
import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import Toast from "react-native-toast-message";
|
||||
|
||||
export default function EventEdit() {
|
||||
const { id } = useLocalSearchParams();
|
||||
const [data, setData] = useState<any>();
|
||||
// {
|
||||
// title: "",
|
||||
// lokasi: "",
|
||||
// deskripsi: "",
|
||||
// eventMaster_TipeAcaraId: "",
|
||||
// tanggal: "",
|
||||
// tanggalSelesai: "",
|
||||
// authorId: "",
|
||||
// }
|
||||
const [listTypeEvent, setListTypeEvent] = useState([]);
|
||||
const [selectedDate, setSelectedDate] = useState<
|
||||
Date | DateTimePickerEvent | null
|
||||
>(null);
|
||||
>();
|
||||
|
||||
const [selectedEndDate, setSelectedEndDate] = useState<
|
||||
Date | DateTimePickerEvent | null
|
||||
>(null);
|
||||
>();
|
||||
|
||||
const handlerSubmit = () => {
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [isLoadData, setIsLoadData] = useState(false);
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
onLoadData();
|
||||
}, [id]),
|
||||
);
|
||||
|
||||
async function onLoadData() {
|
||||
try {
|
||||
if (selectedDate) {
|
||||
console.log("Tanggal yang dipilih:", selectedDate);
|
||||
console.log(`ISO Format ${Platform.OS}:`, selectedDate.toString());
|
||||
// Kirim ke API atau proses lanjutan
|
||||
} else {
|
||||
console.log("Tanggal belum dipilih");
|
||||
}
|
||||
setIsLoadData(true);
|
||||
const response = await apiEventGetOne({ id: id as string });
|
||||
|
||||
if (selectedEndDate) {
|
||||
console.log("Tanggal yang dipilih:", selectedEndDate);
|
||||
console.log(`ISO Format ${Platform.OS}:`, selectedEndDate.toString());
|
||||
// Kirim ke API atau proses lanjutan
|
||||
} else {
|
||||
console.log("Tanggal berakhir belum dipilih");
|
||||
if (response.success) {
|
||||
setData(response.data);
|
||||
setSelectedDate(new Date(response.data.tanggal));
|
||||
setSelectedEndDate(new Date(response.data.tanggalSelesai));
|
||||
}
|
||||
|
||||
console.log("Data berhasil terupdate");
|
||||
router.back()
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
console.log("[ERROR]", error);
|
||||
} finally {
|
||||
setIsLoadData(false);
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
onLoadMasterEventType();
|
||||
}, []);
|
||||
|
||||
const onLoadMasterEventType = async () => {
|
||||
try {
|
||||
const response = await apiMasterEventType();
|
||||
setListTypeEvent(response.data);
|
||||
} catch (error) {
|
||||
console.log("Error onLoadMasterEventType", error);
|
||||
}
|
||||
};
|
||||
|
||||
const buttonSubmit = (
|
||||
<ButtonCustom title="Update" onPress={handlerSubmit} />
|
||||
);
|
||||
const validateDate = async () => {
|
||||
if (
|
||||
data?.title === "" ||
|
||||
data?.lokasi === "" ||
|
||||
data?.deskripsi === "" ||
|
||||
data?.eventMaster_TipeAcaraId === ""
|
||||
) {
|
||||
Toast.show({
|
||||
type: "info",
|
||||
text1: "Info",
|
||||
text2: "Lengkapi semua data",
|
||||
});
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
const startDate = new Date(selectedDate as any);
|
||||
const endDate = new Date(selectedEndDate as any);
|
||||
|
||||
if (!startDate) {
|
||||
Toast.show({
|
||||
type: "info",
|
||||
text1: "Info",
|
||||
text2: "Tanggal mulai tidak valid",
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
if (startDate >= endDate) {
|
||||
Toast.show({
|
||||
type: "info",
|
||||
text1: "Info",
|
||||
text2: "Ubah tanggal berakhirnya event",
|
||||
});
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
const handlerSubmit = async () => {
|
||||
const isValid = await validateDate();
|
||||
if (!isValid) return;
|
||||
|
||||
try {
|
||||
setIsLoading(true);
|
||||
const newData = {
|
||||
...data,
|
||||
tanggal: new Date(selectedDate as any).toISOString(),
|
||||
tanggalSelesai: new Date(selectedEndDate as any).toISOString(),
|
||||
};
|
||||
|
||||
const response = await apiEventUpdateData({
|
||||
id: id as string,
|
||||
data: newData,
|
||||
});
|
||||
|
||||
if (response.success) {
|
||||
Toast.show({
|
||||
type: "success",
|
||||
text1: response.message,
|
||||
});
|
||||
return router.back();
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const validateDateRange = (
|
||||
selectedDate: string | Date,
|
||||
selectedEndDate: string | Date,
|
||||
): { isValid: boolean; error?: string } => {
|
||||
const startDate = new Date(selectedDate);
|
||||
const endDate = new Date(selectedEndDate);
|
||||
|
||||
// Cek apakah tanggal valid
|
||||
if (isNaN(startDate.getTime()) || isNaN(endDate.getTime())) {
|
||||
return {
|
||||
isValid: false,
|
||||
error: "Invalid date provided",
|
||||
};
|
||||
}
|
||||
|
||||
if (startDate >= endDate) {
|
||||
return {
|
||||
isValid: false,
|
||||
error: "Ubah tanggal berakhirnya event",
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
isValid: true,
|
||||
error: undefined,
|
||||
};
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<ViewWrapper>
|
||||
<StackCustom gap={"xs"}>
|
||||
<TextInputCustom
|
||||
placeholder="Masukkan nama event"
|
||||
label="Nama Event"
|
||||
required
|
||||
/>
|
||||
<SelectCustom
|
||||
label="Tipe Event"
|
||||
placeholder="Pilih tipe event"
|
||||
data={masterTypeEvent}
|
||||
onChange={(value) => console.log(value)}
|
||||
/>
|
||||
<TextInputCustom
|
||||
label="Lokasi"
|
||||
placeholder="Masukkan lokasi event"
|
||||
required
|
||||
/>
|
||||
<NewWrapper
|
||||
footerComponent={
|
||||
<BoxButtonOnFooter>
|
||||
<ButtonCustom
|
||||
isLoading={isLoading}
|
||||
title="Update"
|
||||
onPress={handlerSubmit}
|
||||
/>
|
||||
</BoxButtonOnFooter>
|
||||
}
|
||||
>
|
||||
{isLoadData ? (
|
||||
<ListSkeletonComponent />
|
||||
) : (
|
||||
<StackCustom gap={"xs"}>
|
||||
<TextInputCustom
|
||||
placeholder="Masukkan nama event"
|
||||
label="Nama Event"
|
||||
required
|
||||
value={data?.title}
|
||||
onChangeText={(value) => setData({ ...data, title: value })}
|
||||
/>
|
||||
<TextAreaCustom
|
||||
label="Deskripsi"
|
||||
placeholder="Masukkan deskripsi event"
|
||||
required
|
||||
showCount
|
||||
value={data?.deskripsi}
|
||||
onChangeText={(value) => setData({ ...data, deskripsi: value })}
|
||||
/>
|
||||
|
||||
<DateTimePickerCustom
|
||||
label="Tanggal & Waktu Mulai"
|
||||
required
|
||||
onChange={(date: Date) => {
|
||||
setSelectedDate(date as any);
|
||||
}}
|
||||
value={selectedDate as any}
|
||||
minimumDate={new Date(Date.now())}
|
||||
/>
|
||||
<DateTimePickerCustom
|
||||
minimumDate={new Date(Date.now())}
|
||||
label="Tanggal & Waktu Mulai"
|
||||
required
|
||||
value={selectedDate}
|
||||
onChange={(date: any) => {
|
||||
setSelectedDate(date as any);
|
||||
}}
|
||||
/>
|
||||
<StackCustom gap={0}>
|
||||
<DateTimePickerCustom
|
||||
minimumDate={selectedDate as any}
|
||||
label="Tanggal & Waktu Berakhir"
|
||||
required
|
||||
value={selectedEndDate as any}
|
||||
onChange={(date: any) => {
|
||||
setSelectedEndDate(date as any);
|
||||
}}
|
||||
/>
|
||||
|
||||
<DateTimePickerCustom
|
||||
label="Tanggal & Waktu Berakhir"
|
||||
required
|
||||
onChange={(date: Date) => {
|
||||
setSelectedEndDate(date as any);
|
||||
}}
|
||||
value={selectedEndDate as any}
|
||||
/>
|
||||
{/* Muncul */}
|
||||
{validateDateRange(selectedDate as any, selectedEndDate as any)
|
||||
.isValid ? (
|
||||
<TextCustom style={{ color: "green" }}>
|
||||
{
|
||||
validateDateRange(
|
||||
selectedDate as any,
|
||||
selectedEndDate as any,
|
||||
).error
|
||||
}
|
||||
</TextCustom>
|
||||
) : (
|
||||
<TextCustom style={{ color: "red" }}>
|
||||
{
|
||||
validateDateRange(
|
||||
selectedDate as any,
|
||||
selectedEndDate as any,
|
||||
).error
|
||||
}
|
||||
</TextCustom>
|
||||
)}
|
||||
{/* <Spacing /> */}
|
||||
</StackCustom>
|
||||
|
||||
<TextAreaCustom
|
||||
label="Deskripsi"
|
||||
placeholder="Masukkan deskripsi event"
|
||||
required
|
||||
showCount
|
||||
maxLength={100}
|
||||
/>
|
||||
|
||||
{buttonSubmit}
|
||||
</StackCustom>
|
||||
</ViewWrapper>
|
||||
<SelectCustom
|
||||
label="Tipe Event"
|
||||
placeholder="Pilih tipe event"
|
||||
data={listTypeEvent.map((item: any) => ({
|
||||
label: item.name,
|
||||
value: item.id,
|
||||
}))}
|
||||
value={data?.eventMaster_TipeAcaraId || ""}
|
||||
onChange={(value) => {
|
||||
console.log(value);
|
||||
setData({ ...data, eventMaster_TipeAcaraId: value });
|
||||
}}
|
||||
/>
|
||||
<TextInputCustom
|
||||
label="Lokasi"
|
||||
placeholder="Masukkan lokasi event"
|
||||
required
|
||||
value={data?.lokasi}
|
||||
onChangeText={(value) => setData({ ...data, lokasi: value })}
|
||||
/>
|
||||
</StackCustom>
|
||||
)}
|
||||
</NewWrapper>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
DotButton,
|
||||
DrawerCustom,
|
||||
@@ -9,12 +10,29 @@ import { IMenuDrawerItem } from "@/components/_Interface/types";
|
||||
import LeftButtonCustom from "@/components/Button/BackButton";
|
||||
import Event_BoxDetailPublishSection from "@/screens/Event/BoxDetailPublishSection";
|
||||
import { menuDrawerPublishEvent } from "@/screens/Event/menuDrawerPublish";
|
||||
import { apiEventGetOne } from "@/service/api-client/api-event";
|
||||
import { router, Stack, useLocalSearchParams } from "expo-router";
|
||||
import { useState } from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
export default function EventDetailHistory() {
|
||||
const { id } = useLocalSearchParams();
|
||||
const [openDrawer, setOpenDrawer] = useState(false);
|
||||
const [data, setData] = useState();
|
||||
|
||||
useEffect(() => {
|
||||
onLoadData();
|
||||
}, [id]);
|
||||
|
||||
const onLoadData = async () => {
|
||||
try {
|
||||
const response = await apiEventGetOne({ id: id as string });
|
||||
if (response.success) {
|
||||
setData(response.data);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
}
|
||||
};
|
||||
|
||||
const handlePress = (item: IMenuDrawerItem) => {
|
||||
console.log("PATH ", item.path);
|
||||
@@ -32,18 +50,18 @@ export default function EventDetailHistory() {
|
||||
}}
|
||||
/>
|
||||
<ViewWrapper>
|
||||
<Event_BoxDetailPublishSection />
|
||||
<Event_BoxDetailPublishSection data={data} />
|
||||
<Spacing />
|
||||
</ViewWrapper>
|
||||
<DrawerCustom
|
||||
isVisible={openDrawer}
|
||||
closeDrawer={() => setOpenDrawer(false)}
|
||||
height={250}
|
||||
height={"auto"}
|
||||
>
|
||||
<MenuDrawerDynamicGrid
|
||||
data={menuDrawerPublishEvent({ id: id as string })}
|
||||
columns={4}
|
||||
onPressItem={handlePress}
|
||||
onPressItem={handlePress as any}
|
||||
/>
|
||||
</DrawerCustom>
|
||||
</>
|
||||
|
||||
@@ -1,17 +1,10 @@
|
||||
import {
|
||||
AvatarUsernameAndOtherComponent,
|
||||
BaseBox,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import Event_ScreenListOfParticipants from "@/screens/Event/ScreenListOfParticipants";
|
||||
|
||||
export default function EventListOfParticipants() {
|
||||
return (
|
||||
<ViewWrapper>
|
||||
{Array.from({ length: 10 }).map((_, index) => (
|
||||
<BaseBox key={index} paddingBlock={0}>
|
||||
<AvatarUsernameAndOtherComponent avatarHref={`/profile/${index}`} />
|
||||
</BaseBox>
|
||||
))}
|
||||
</ViewWrapper>
|
||||
<>
|
||||
<Event_ScreenListOfParticipants />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,22 +1,77 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
ButtonCustom,
|
||||
DotButton,
|
||||
DrawerCustom,
|
||||
MenuDrawerDynamicGrid,
|
||||
Spacing,
|
||||
ViewWrapper
|
||||
AlertDefaultSystem,
|
||||
BackButton,
|
||||
ButtonCustom,
|
||||
DotButton,
|
||||
DrawerCustom,
|
||||
MenuDrawerDynamicGrid,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import { IMenuDrawerItem } from "@/components/_Interface/types";
|
||||
import CustomSkeleton from "@/components/_ShareComponent/SkeletonCustom";
|
||||
import LeftButtonCustom from "@/components/Button/BackButton";
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
import Event_BoxDetailPublishSection from "@/screens/Event/BoxDetailPublishSection";
|
||||
import { menuDrawerPublishEvent } from "@/screens/Event/menuDrawerPublish";
|
||||
import { router, Stack, useLocalSearchParams } from "expo-router";
|
||||
import { useState } from "react";
|
||||
import { Alert } from "react-native";
|
||||
import {
|
||||
apiEventCheckParticipants,
|
||||
apiEventGetOne,
|
||||
apiEventJoin,
|
||||
} from "@/service/api-client/api-event";
|
||||
import dayjs from "dayjs";
|
||||
import {
|
||||
Redirect,
|
||||
router,
|
||||
Stack,
|
||||
useFocusEffect,
|
||||
useLocalSearchParams,
|
||||
} from "expo-router";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import Toast from "react-native-toast-message";
|
||||
|
||||
export default function EventDetailPublish() {
|
||||
const now = new Date().toISOString();
|
||||
const { user } = useAuth();
|
||||
const { id } = useLocalSearchParams();
|
||||
const [openDrawer, setOpenDrawer] = useState(false);
|
||||
const [isLoadingData, setIsLoadingData] = useState(false);
|
||||
const [isLoadingJoin, setIsLoadingJoin] = useState(false);
|
||||
|
||||
const [data, setData] = useState<any>();
|
||||
const [isParticipant, setIsParticipant] = useState<boolean | null>(null);
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
onLoadData();
|
||||
}, [])
|
||||
);
|
||||
|
||||
async function onLoadData() {
|
||||
try {
|
||||
setIsLoadingData(true);
|
||||
const response = await apiEventGetOne({ id: id as string });
|
||||
if (response.success) {
|
||||
setData(response.data);
|
||||
|
||||
const responseCheckParticipants = await apiEventCheckParticipants({
|
||||
id: id as string,
|
||||
userId: user?.id as string,
|
||||
});
|
||||
|
||||
if (
|
||||
responseCheckParticipants.success &&
|
||||
responseCheckParticipants.data
|
||||
) {
|
||||
setIsParticipant(true);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
} finally {
|
||||
setIsLoadingData(false);
|
||||
}
|
||||
}
|
||||
|
||||
const handlePress = (item: IMenuDrawerItem) => {
|
||||
console.log("PATH ", item.path);
|
||||
@@ -24,39 +79,108 @@ export default function EventDetailPublish() {
|
||||
setOpenDrawer(false);
|
||||
};
|
||||
|
||||
const footerButton = (
|
||||
<ButtonCustom
|
||||
backgroundColor="green"
|
||||
textColor="white"
|
||||
onPress={() => Alert.alert("Anda berhasil join event ini")}
|
||||
>
|
||||
Join
|
||||
</ButtonCustom>
|
||||
);
|
||||
const handlerJoin = async () => {
|
||||
const userId = user?.id;
|
||||
if (!userId) {
|
||||
return Toast.show({
|
||||
type: "error",
|
||||
text2: "Anda belum login",
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
setIsLoadingJoin(true);
|
||||
const response = await apiEventJoin({
|
||||
id: id as string,
|
||||
userId: userId as string,
|
||||
});
|
||||
if (response.success) {
|
||||
router.navigate(
|
||||
`/(application)/(user)/event/${id}/list-of-participants`
|
||||
);
|
||||
Toast.show({
|
||||
type: "success",
|
||||
text1: "Anda berhasil join",
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
} finally {
|
||||
setIsLoadingJoin(false);
|
||||
}
|
||||
};
|
||||
|
||||
const isEventFinished =
|
||||
id && data?.tanggalSelesai && dayjs(data.tanggalSelesai).isBefore(now);
|
||||
|
||||
useEffect(() => {
|
||||
if (isEventFinished) {
|
||||
router.replace(`/(application)/(user)/event/${id}/history`);
|
||||
}
|
||||
}, [isEventFinished, id]);
|
||||
|
||||
if (isEventFinished) {
|
||||
return (
|
||||
<ViewWrapper>
|
||||
<CustomSkeleton />
|
||||
</ViewWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
const FooterButton = () => {
|
||||
return (
|
||||
<>
|
||||
<ButtonCustom
|
||||
disabled={isParticipant as any}
|
||||
isLoading={isLoadingJoin}
|
||||
backgroundColor="green"
|
||||
textColor="white"
|
||||
onPress={() =>
|
||||
AlertDefaultSystem({
|
||||
title: "Join event",
|
||||
message: "Anda yakin ingin join sebagai peserta event ?",
|
||||
textLeft: "Tidak",
|
||||
textRight: "Ya",
|
||||
onPressLeft: () => {},
|
||||
onPressRight: () => handlerJoin(),
|
||||
})
|
||||
}
|
||||
>
|
||||
{isParticipant ? "Anda sudah tergabung" : "Join"}
|
||||
</ButtonCustom>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Stack.Screen
|
||||
options={{
|
||||
title: `Event publish`,
|
||||
headerLeft: () => <LeftButtonCustom />,
|
||||
title: `Event Publish`,
|
||||
headerLeft: () => <BackButton onPress={() => router.back()} />,
|
||||
headerRight: () => <DotButton onPress={() => setOpenDrawer(true)} />,
|
||||
}}
|
||||
/>
|
||||
<ViewWrapper>
|
||||
<Event_BoxDetailPublishSection footerButton={footerButton} />
|
||||
<Spacing />
|
||||
{isLoadingData ? (
|
||||
<CustomSkeleton height={400} />
|
||||
) : (
|
||||
<Event_BoxDetailPublishSection
|
||||
data={data}
|
||||
footerButton={FooterButton()}
|
||||
/>
|
||||
)}
|
||||
</ViewWrapper>
|
||||
|
||||
<DrawerCustom
|
||||
isVisible={openDrawer}
|
||||
closeDrawer={() => setOpenDrawer(false)}
|
||||
height={250}
|
||||
height={"auto"}
|
||||
>
|
||||
<MenuDrawerDynamicGrid
|
||||
data={menuDrawerPublishEvent({ id: id as string })}
|
||||
columns={4}
|
||||
onPressItem={handlePress}
|
||||
onPressItem={handlePress as any}
|
||||
/>
|
||||
</DrawerCustom>
|
||||
</>
|
||||
|
||||
@@ -1,19 +1,52 @@
|
||||
import {
|
||||
BoxButtonOnFooter,
|
||||
ButtonCustom,
|
||||
NewWrapper,
|
||||
SelectCustom,
|
||||
Spacing,
|
||||
StackCustom,
|
||||
TextAreaCustom,
|
||||
TextCustom,
|
||||
TextInputCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import DateTimePickerCustom from "@/components/DateInput/DateTimePickerCustom";
|
||||
import { masterTypeEvent } from "@/lib/dummy-data/event/master-type-event";
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
import { apiEventCreate } from "@/service/api-client/api-event";
|
||||
import { apiMasterEventType } from "@/service/api-client/api-master";
|
||||
import { DateTimePickerEvent } from "@react-native-community/datetimepicker";
|
||||
import { router } from "expo-router";
|
||||
import React, { useState } from "react";
|
||||
import { Platform } from "react-native";
|
||||
import { useEffect, useState } from "react";
|
||||
import Toast from "react-native-toast-message";
|
||||
|
||||
interface EventCreateProps {
|
||||
title?: string;
|
||||
lokasi?: string;
|
||||
deskripsi?: string;
|
||||
eventMaster_TipeAcaraId?: string;
|
||||
tanggal?: string;
|
||||
tanggalSelesai?: string;
|
||||
authorId?: string;
|
||||
}
|
||||
|
||||
export default function EventCreate() {
|
||||
const [data, setData] = useState<EventCreateProps>();
|
||||
const [listTypeEvent, setListTypeEvent] = useState([]);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const { user } = useAuth();
|
||||
|
||||
useEffect(() => {
|
||||
onLoadMasterEventType();
|
||||
}, []);
|
||||
|
||||
const onLoadMasterEventType = async () => {
|
||||
try {
|
||||
const response = await apiMasterEventType();
|
||||
setListTypeEvent(response.data);
|
||||
} catch (error) {
|
||||
console.log("Error onLoadMasterEventType", error);
|
||||
}
|
||||
};
|
||||
|
||||
const [selectedDate, setSelectedDate] = useState<
|
||||
Date | DateTimePickerEvent | null
|
||||
>(null);
|
||||
@@ -22,56 +55,83 @@ export default function EventCreate() {
|
||||
Date | DateTimePickerEvent | null
|
||||
>(null);
|
||||
|
||||
const handlerSubmit = () => {
|
||||
const handlerSubmit = async () => {
|
||||
if (
|
||||
!data?.title ||
|
||||
!data?.lokasi ||
|
||||
!data?.deskripsi ||
|
||||
!data?.eventMaster_TipeAcaraId
|
||||
) {
|
||||
Toast.show({
|
||||
type: "info",
|
||||
text1: "Info",
|
||||
text2: "Lengkapi semua data",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (!selectedDate || !selectedEndDate) {
|
||||
Toast.show({
|
||||
type: "info",
|
||||
text1: "Info",
|
||||
text2: "Pilih tanggal mulai dan berakhir",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (selectedDate) {
|
||||
console.log("Tanggal yang dipilih:", selectedDate);
|
||||
console.log(`ISO Format ${Platform.OS}:`, selectedDate.toString());
|
||||
// Kirim ke API atau proses lanjutan
|
||||
} else {
|
||||
console.log("Tanggal belum dipilih");
|
||||
}
|
||||
setIsLoading(true);
|
||||
|
||||
if (selectedEndDate) {
|
||||
console.log("Tanggal yang dipilih:", selectedEndDate);
|
||||
console.log(`ISO Format ${Platform.OS}:`, selectedEndDate.toString());
|
||||
// Kirim ke API atau proses lanjutan
|
||||
} else {
|
||||
console.log("Tanggal berakhir belum dipilih");
|
||||
}
|
||||
const newData = {
|
||||
...data,
|
||||
tanggal: new Date(selectedDate as any).toISOString(),
|
||||
tanggalSelesai: new Date(selectedEndDate as any).toISOString(),
|
||||
authorId: user?.id,
|
||||
};
|
||||
|
||||
console.log("Data berhasil disimpan");
|
||||
router.navigate("/event/status");
|
||||
console.log("Data berhasil disimpan", JSON.stringify(newData, null, 2));
|
||||
|
||||
const response = await apiEventCreate(newData);
|
||||
console.log("Response", JSON.stringify(response, null, 2));
|
||||
|
||||
router.replace("/event/status?status=review");
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const buttonSubmit = (
|
||||
<ButtonCustom title="Simpan" onPress={handlerSubmit} />
|
||||
// <BoxButtonOnFooter>
|
||||
// </BoxButtonOnFooter>
|
||||
<ButtonCustom
|
||||
isLoading={isLoading}
|
||||
title="Simpan"
|
||||
onPress={handlerSubmit}
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<ViewWrapper>
|
||||
<NewWrapper
|
||||
footerComponent={<BoxButtonOnFooter>{buttonSubmit}</BoxButtonOnFooter>}
|
||||
>
|
||||
<StackCustom gap={"xs"}>
|
||||
<TextInputCustom
|
||||
placeholder="Masukkan nama event"
|
||||
label="Nama Event"
|
||||
required
|
||||
onChangeText={(value: any) => setData({ ...data, title: value })}
|
||||
/>
|
||||
<SelectCustom
|
||||
label="Tipe Event"
|
||||
placeholder="Pilih tipe event"
|
||||
data={masterTypeEvent}
|
||||
onChange={(value) => console.log(value)}
|
||||
/>
|
||||
<TextInputCustom
|
||||
label="Lokasi"
|
||||
placeholder="Masukkan lokasi event"
|
||||
|
||||
<TextAreaCustom
|
||||
label="Deskripsi"
|
||||
placeholder="Masukkan deskripsi event"
|
||||
required
|
||||
showCount
|
||||
value={data?.deskripsi || ""}
|
||||
onChangeText={(value: any) =>
|
||||
setData({ ...data, deskripsi: value })
|
||||
}
|
||||
/>
|
||||
|
||||
<DateTimePickerCustom
|
||||
@@ -84,26 +144,45 @@ export default function EventCreate() {
|
||||
minimumDate={new Date(Date.now())}
|
||||
/>
|
||||
|
||||
<DateTimePickerCustom
|
||||
label="Tanggal & Waktu Berakhir"
|
||||
required
|
||||
onChange={(date: Date) => {
|
||||
setSelectedEndDate(date as any);
|
||||
}}
|
||||
value={selectedEndDate as any}
|
||||
/>
|
||||
<StackCustom gap={0}>
|
||||
<DateTimePickerCustom
|
||||
disabled={!selectedDate}
|
||||
label="Tanggal & Waktu Berakhir"
|
||||
required
|
||||
onChange={(date: Date) => {
|
||||
setSelectedEndDate(date as any);
|
||||
}}
|
||||
value={selectedEndDate as any}
|
||||
minimumDate={new Date(selectedDate as any)}
|
||||
/>
|
||||
{!selectedDate && (
|
||||
<TextCustom color="gray" size={"small"}>
|
||||
Note: Pilih tanggal mulai terlebih dahulu
|
||||
</TextCustom>
|
||||
)}
|
||||
<Spacing />
|
||||
<SelectCustom
|
||||
label="Tipe Event"
|
||||
placeholder="Pilih tipe event"
|
||||
data={listTypeEvent.map((item: any) => ({
|
||||
label: item.name,
|
||||
value: item.id,
|
||||
}))}
|
||||
value={data?.eventMaster_TipeAcaraId || null}
|
||||
onChange={(value: any) =>
|
||||
setData({ ...data, eventMaster_TipeAcaraId: value })
|
||||
}
|
||||
/>
|
||||
|
||||
<TextAreaCustom
|
||||
label="Deskripsi"
|
||||
placeholder="Masukkan deskripsi event"
|
||||
required
|
||||
showCount
|
||||
maxLength={100}
|
||||
/>
|
||||
|
||||
{buttonSubmit}
|
||||
<TextInputCustom
|
||||
label="Lokasi"
|
||||
placeholder="Masukkan lokasi event"
|
||||
required
|
||||
onChangeText={(value: any) => setData({ ...data, lokasi: value })}
|
||||
/>
|
||||
</StackCustom>
|
||||
</StackCustom>
|
||||
</ViewWrapper>
|
||||
</NewWrapper>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||