Compare commits
60 Commits
api-forum/
...
qc/14-nov-
| Author | SHA1 | Date | |
|---|---|---|---|
| 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 |
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
|
||||
@@ -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)
|
||||
@@ -94,6 +94,8 @@ android {
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
versionCode 1
|
||||
versionName "1.0.0"
|
||||
|
||||
buildConfigField "String", "REACT_NATIVE_RELEASE_LEVEL", "\"${findProperty('reactNativeReleaseLevel') ?: 'stable'}\""
|
||||
}
|
||||
signingConfigs {
|
||||
debug {
|
||||
@@ -111,15 +113,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 {
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
<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">
|
||||
<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"/>
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 55 KiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 84 KiB |
|
Before Width: | Height: | Size: 97 KiB After Width: | Height: | Size: 154 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 |
@@ -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>
|
||||
@@ -12,21 +12,8 @@ buildscript {
|
||||
}
|
||||
}
|
||||
|
||||
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' }
|
||||
|
||||
@@ -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,61 +1,79 @@
|
||||
// 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",
|
||||
infoPlist: {
|
||||
ITSAppUsesNonExemptEncryption: false,
|
||||
},
|
||||
associatedDomains: ["applinks:cld-dkr-staging-hipmi.wibudev.com"],
|
||||
buildNumber: "5",
|
||||
},
|
||||
|
||||
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: 2,
|
||||
// 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-web-browser',
|
||||
"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",
|
||||
],
|
||||
|
||||
experiments: {
|
||||
@@ -65,10 +83,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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@ export default function UserLayout() {
|
||||
return (
|
||||
<>
|
||||
<Stack screenOptions={HeaderStyles}>
|
||||
|
||||
<Stack.Screen
|
||||
name="waiting-room"
|
||||
options={{
|
||||
@@ -186,7 +185,7 @@ export default function UserLayout() {
|
||||
name="crowdfunding/index"
|
||||
options={{
|
||||
title: "Crowdfunding",
|
||||
headerLeft: () => <BackButton />,
|
||||
headerLeft: () => <BackButton path="/home" />,
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -449,7 +448,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: () => (
|
||||
@@ -463,7 +462,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: () => (
|
||||
@@ -477,14 +476,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 />,
|
||||
|
||||
@@ -19,7 +19,6 @@ import Toast from "react-native-toast-message";
|
||||
|
||||
export default function CollaborationEdit() {
|
||||
const { id } = useLocalSearchParams();
|
||||
console.log("id :", id);
|
||||
const [data, setData] = useState<any>();
|
||||
const [listMaster, setListMaster] = useState<any[]>([]);
|
||||
const [loadingData, setLoadingData] = useState(false);
|
||||
|
||||
@@ -1,11 +1,41 @@
|
||||
import {
|
||||
FloatingButton,
|
||||
ViewWrapper
|
||||
LoaderCustom,
|
||||
TextCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import Donation_BoxPublish from "@/screens/Donation/BoxPublish";
|
||||
import { router } from "expo-router";
|
||||
import { apiDonationGetAll } from "@/service/api-client/api-donation";
|
||||
import { router, useFocusEffect } from "expo-router";
|
||||
import _ from "lodash";
|
||||
import { useCallback, useState } from "react";
|
||||
|
||||
export default function DonationBeranda() {
|
||||
const [list, setList] = useState<any[] | null>(null);
|
||||
const [loadList, setLoadList] = useState(false);
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
onLoadData();
|
||||
}, [])
|
||||
);
|
||||
|
||||
const onLoadData = async () => {
|
||||
try {
|
||||
setLoadList(true);
|
||||
const response = await apiDonationGetAll({
|
||||
category: "beranda"
|
||||
});
|
||||
console.log("[RES GET ALL]", JSON.stringify(response.data, null, 2));
|
||||
|
||||
setList(response.data);
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
} finally {
|
||||
setLoadList(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<ViewWrapper
|
||||
hideFooter
|
||||
@@ -13,9 +43,15 @@ export default function DonationBeranda() {
|
||||
<FloatingButton onPress={() => router.push("/donation/create")} />
|
||||
}
|
||||
>
|
||||
{Array.from({ length: 10 }).map((_, index) => (
|
||||
<Donation_BoxPublish key={index} id={index.toString()}/>
|
||||
))}
|
||||
{loadList ? (
|
||||
<LoaderCustom />
|
||||
) : _.isEmpty(list) ? (
|
||||
<TextCustom align="center" color="gray">Belum ada donasi</TextCustom>
|
||||
) : (
|
||||
list?.map((item: any, index: number) => (
|
||||
<Donation_BoxPublish data={item} key={index} id={item.id} />
|
||||
))
|
||||
)}
|
||||
</ViewWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,76 +1,142 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
BadgeCustom,
|
||||
BaseBox,
|
||||
DummyLandscapeImage,
|
||||
Grid,
|
||||
LoaderCustom,
|
||||
StackCustom,
|
||||
TextCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import { dummyMasterStatusTransaction } from "@/lib/dummy-data/_master/status-transaction";
|
||||
import { router } from "expo-router";
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
import { apiDonationGetAll } from "@/service/api-client/api-donation";
|
||||
import { formatCurrencyDisplay } from "@/utils/formatCurrencyDisplay";
|
||||
import { Href, router, useFocusEffect } from "expo-router";
|
||||
import _ from "lodash";
|
||||
import { useCallback, useState } from "react";
|
||||
import { View } from "react-native";
|
||||
|
||||
export default function DonationMyDonation() {
|
||||
const randomStatusData = Array.from({ length: 10 }, () => {
|
||||
const randomIndex = Math.floor(
|
||||
Math.random() * dummyMasterStatusTransaction.length
|
||||
);
|
||||
return dummyMasterStatusTransaction[randomIndex];
|
||||
});
|
||||
const { user } = useAuth();
|
||||
const [list, setList] = useState<any[] | null>(null);
|
||||
const [loadList, setLoadList] = useState(false);
|
||||
|
||||
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`);
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
onLoadData();
|
||||
}, [user?.id])
|
||||
);
|
||||
|
||||
const onLoadData = async () => {
|
||||
try {
|
||||
setLoadList(true);
|
||||
const response = await apiDonationGetAll({
|
||||
category: "my-donation",
|
||||
authorId: user?.id,
|
||||
});
|
||||
console.log(
|
||||
"[RES GET MY DONATION]",
|
||||
JSON.stringify(response.data, null, 2)
|
||||
);
|
||||
|
||||
setList(response.data);
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
} finally {
|
||||
setLoadList(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handlerColor = (status: string) => {
|
||||
if (status === "menunggu") {
|
||||
return "orange";
|
||||
} else if (status === "proses") {
|
||||
return "white";
|
||||
} else if (status === "berhasil") {
|
||||
return "green";
|
||||
} else if (status === "gagal") {
|
||||
return "red";
|
||||
}
|
||||
};
|
||||
|
||||
const handlePress = ({
|
||||
invoiceId,
|
||||
donationId,
|
||||
status,
|
||||
}: {
|
||||
invoiceId: string;
|
||||
donationId: string;
|
||||
status: string;
|
||||
}) => {
|
||||
const url: Href = `../${donationId}/(transaction-flow)/${invoiceId}`;
|
||||
if (status === "menunggu") {
|
||||
router.push(`${url}/invoice`);
|
||||
} else if (status === "proses") {
|
||||
router.push(`${url}/process`);
|
||||
} else if (status === "berhasil") {
|
||||
router.push(`${url}/success`);
|
||||
} else if (status === "gagal") {
|
||||
router.push(`${url}/failed`);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<ViewWrapper hideFooter>
|
||||
{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.
|
||||
{loadList ? (
|
||||
<LoaderCustom />
|
||||
) : _.isEmpty(list) ? (
|
||||
<TextCustom align="center" color="gray">
|
||||
Belum ada transaksi
|
||||
</TextCustom>
|
||||
) : (
|
||||
list?.map((item, index) => (
|
||||
<BaseBox
|
||||
key={index}
|
||||
paddingTop={7}
|
||||
paddingBottom={7}
|
||||
onPress={() => {
|
||||
handlePress({
|
||||
status: _.lowerCase(item.statusInvoice),
|
||||
invoiceId: item.id,
|
||||
donationId: item.donasiId,
|
||||
});
|
||||
}}
|
||||
>
|
||||
<Grid>
|
||||
<Grid.Col span={5}>
|
||||
<DummyLandscapeImage
|
||||
height={100}
|
||||
unClickPath
|
||||
imageId={item.imageId}
|
||||
/>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={1}>
|
||||
<View />
|
||||
</Grid.Col>
|
||||
<Grid.Col span={6}>
|
||||
<StackCustom>
|
||||
<TextCustom truncate={2} bold>
|
||||
{item.title || "-"}
|
||||
</TextCustom>
|
||||
</View>
|
||||
<View>
|
||||
<TextCustom>Donasi Saya</TextCustom>
|
||||
|
||||
<TextCustom bold color="yellow">
|
||||
Rp. 7.500.000
|
||||
Rp. {formatCurrencyDisplay(item.nominal)}
|
||||
</TextCustom>
|
||||
</View>
|
||||
<BadgeCustom variant="light" color={item.color} fullWidth>
|
||||
{item.label}
|
||||
</BadgeCustom>
|
||||
</StackCustom>
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
</BaseBox>
|
||||
))}
|
||||
|
||||
<BadgeCustom
|
||||
variant="light"
|
||||
color={handlerColor(_.lowerCase(item.statusInvoice))}
|
||||
fullWidth
|
||||
>
|
||||
{item.statusInvoice}
|
||||
</BadgeCustom>
|
||||
</StackCustom>
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
</BaseBox>
|
||||
))
|
||||
)}
|
||||
</ViewWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,12 +1,48 @@
|
||||
import { ScrollableCustom, ViewWrapper } from "@/components";
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
LoaderCustom,
|
||||
ScrollableCustom,
|
||||
TextCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
import { dummyMasterStatus } from "@/lib/dummy-data/_master/status";
|
||||
import Donasi_BoxStatus from "@/screens/Donation/BoxStatus";
|
||||
import { useState } from "react";
|
||||
import { apiDonationGetByStatus } from "@/service/api-client/api-donation";
|
||||
import { useFocusEffect } from "expo-router";
|
||||
import _ from "lodash";
|
||||
import { useCallback, useState } from "react";
|
||||
|
||||
export default function DonationStatus() {
|
||||
const { user } = useAuth();
|
||||
const [activeCategory, setActiveCategory] = useState<string | null>(
|
||||
"publish"
|
||||
);
|
||||
const [listData, setListData] = useState<any[] | null>(null);
|
||||
const [loadList, setLoadList] = useState(false);
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
onLoadList();
|
||||
}, [activeCategory])
|
||||
);
|
||||
|
||||
const onLoadList = async () => {
|
||||
try {
|
||||
setLoadList(true);
|
||||
const response = await apiDonationGetByStatus({
|
||||
authorId: user?.id as string,
|
||||
status: activeCategory as string,
|
||||
});
|
||||
|
||||
setListData(response.data);
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
setListData(null);
|
||||
} finally {
|
||||
setLoadList(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handlePress = (item: any) => {
|
||||
setActiveCategory(item.value);
|
||||
@@ -26,13 +62,19 @@ export default function DonationStatus() {
|
||||
);
|
||||
return (
|
||||
<ViewWrapper hideFooter headerComponent={scrollComponent}>
|
||||
{Array.from({ length: 10 }).map((_, index) => (
|
||||
<Donasi_BoxStatus
|
||||
key={index}
|
||||
id={index.toString()}
|
||||
status={activeCategory as string}
|
||||
/>
|
||||
))}
|
||||
{loadList ? (
|
||||
<LoaderCustom />
|
||||
) : _.isEmpty(listData) ? (
|
||||
<TextCustom align="center">Tidak ada data {activeCategory}</TextCustom>
|
||||
) : (
|
||||
listData?.map((item: any, index: number) => (
|
||||
<Donasi_BoxStatus
|
||||
key={index}
|
||||
data={item}
|
||||
status={activeCategory as string}
|
||||
/>
|
||||
))
|
||||
)}
|
||||
</ViewWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
ButtonCenteredOnly,
|
||||
ButtonCustom,
|
||||
@@ -9,17 +10,120 @@ 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>
|
||||
<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 +134,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,12 +143,16 @@ export default function DonationEditNews() {
|
||||
required
|
||||
showCount
|
||||
maxLength={1000}
|
||||
value={data?.deskripsi}
|
||||
onChangeText={(value) => setData({ ...data, deskripsi: value })}
|
||||
/>
|
||||
|
||||
<Spacing />
|
||||
<ButtonCustom
|
||||
disabled={!data?.title || !data?.deskripsi}
|
||||
isLoading={isLoading}
|
||||
onPress={() => {
|
||||
router.back();
|
||||
handlerSubmitUpdate();
|
||||
}}
|
||||
>
|
||||
Update
|
||||
|
||||
@@ -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();
|
||||
},
|
||||
});
|
||||
|
||||
@@ -9,17 +9,79 @@ import {
|
||||
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>
|
||||
<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 +92,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,12 +106,21 @@ export default function DonationAddNews() {
|
||||
required
|
||||
showCount
|
||||
maxLength={1000}
|
||||
value={data.deskripsi}
|
||||
onChangeText={(value) => {
|
||||
setData({
|
||||
...data,
|
||||
deskripsi: value,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
|
||||
|
||||
<Spacing />
|
||||
<ButtonCustom
|
||||
disabled={!data.title || !data.deskripsi}
|
||||
isLoading={isLoading}
|
||||
onPress={() => {
|
||||
router.back();
|
||||
handlerSubmit();
|
||||
}}
|
||||
>
|
||||
Simpan
|
||||
|
||||
@@ -1,45 +1,88 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
BackButton,
|
||||
BaseBox,
|
||||
DrawerCustom,
|
||||
Grid,
|
||||
MenuDrawerDynamicGrid,
|
||||
TextCustom,
|
||||
ViewWrapper
|
||||
BackButton,
|
||||
BaseBox,
|
||||
DrawerCustom,
|
||||
Grid,
|
||||
LoaderCustom,
|
||||
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 { apiDonationGetNewsById } from "@/service/api-client/api-donation";
|
||||
import { formatChatTime } from "@/utils/formatChatTime";
|
||||
import {
|
||||
router,
|
||||
Stack,
|
||||
useFocusEffect,
|
||||
useLocalSearchParams,
|
||||
} from "expo-router";
|
||||
import _ from "lodash";
|
||||
import { useCallback, useState } from "react";
|
||||
|
||||
export default function DonationRecapOfNews() {
|
||||
const { id } = useLocalSearchParams();
|
||||
const [openDrawer, setOpenDrawer] = useState(false);
|
||||
const [list, setList] = useState<any[] | null>(null);
|
||||
const [loadList, setLoadList] = useState<boolean>(false);
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
onLoadList();
|
||||
}, [id])
|
||||
);
|
||||
|
||||
const onLoadList = async () => {
|
||||
try {
|
||||
setLoadList(true);
|
||||
const response = await apiDonationGetNewsById({
|
||||
id: id as string,
|
||||
category: "get-all",
|
||||
});
|
||||
|
||||
setList(response.data);
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
setList([]);
|
||||
} finally {
|
||||
setLoadList(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Stack.Screen
|
||||
options={{
|
||||
title: "Daftar Kabar",
|
||||
headerLeft: () => <BackButton />, }}
|
||||
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>
|
||||
))}
|
||||
{loadList ? (
|
||||
<LoaderCustom />
|
||||
) : _.isEmpty(list) ? (
|
||||
<TextCustom align="center" color="gray">
|
||||
Tidak ada kabar
|
||||
</TextCustom>
|
||||
) : (
|
||||
list?.map((item: any, index: number) => (
|
||||
<BaseBox key={index} href={`/donation/[id]/(news)/${item.id}`}>
|
||||
<Grid>
|
||||
<Grid.Col span={8}>
|
||||
<TextCustom truncate bold>
|
||||
{item?.title || "-"}
|
||||
</TextCustom>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={4} style={{ alignItems: "flex-end" }}>
|
||||
<TextCustom size="small">
|
||||
{formatChatTime(item?.createdAt)}
|
||||
</TextCustom>
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
</BaseBox>
|
||||
))
|
||||
)}
|
||||
</ViewWrapper>
|
||||
|
||||
<DrawerCustom
|
||||
|
||||
@@ -1,21 +1,55 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
BackButton,
|
||||
BaseBox,
|
||||
DotButton,
|
||||
DrawerCustom,
|
||||
Grid,
|
||||
LoaderCustom,
|
||||
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 { apiDonationGetNewsById } from "@/service/api-client/api-donation";
|
||||
import { formatChatTime } from "@/utils/formatChatTime";
|
||||
import {
|
||||
router,
|
||||
Stack,
|
||||
useFocusEffect,
|
||||
useLocalSearchParams,
|
||||
} from "expo-router";
|
||||
import _ from "lodash";
|
||||
import { useCallback, useState } from "react";
|
||||
|
||||
export default function DonationRecapOfNews() {
|
||||
const { id } = useLocalSearchParams();
|
||||
const [openDrawer, setOpenDrawer] = useState(false);
|
||||
const [list, setList] = useState<any[] | null>(null);
|
||||
const [loadList, setLoadList] = useState<boolean>(false);
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
onLoadList();
|
||||
}, [id])
|
||||
);
|
||||
|
||||
const onLoadList = async () => {
|
||||
try {
|
||||
setLoadList(true);
|
||||
const response = await apiDonationGetNewsById({
|
||||
id: id as string,
|
||||
category: "get-all",
|
||||
});
|
||||
|
||||
setList(response.data);
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
setList([]);
|
||||
} finally {
|
||||
setLoadList(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -27,20 +61,30 @@ export default function DonationRecapOfNews() {
|
||||
}}
|
||||
/>
|
||||
<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>
|
||||
))}
|
||||
{loadList ? (
|
||||
<LoaderCustom />
|
||||
) : _.isEmpty(list) ? (
|
||||
<TextCustom align="center" color="gray">
|
||||
Tidak ada kabar
|
||||
</TextCustom>
|
||||
) : (
|
||||
list?.map((item: any, index: number) => (
|
||||
<BaseBox key={index} href={`/donation/[id]/(news)/${item.id}`}>
|
||||
<Grid>
|
||||
<Grid.Col span={8}>
|
||||
<TextCustom truncate bold>
|
||||
{item?.title || "-"}
|
||||
</TextCustom>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={4} style={{ alignItems: "flex-end" }}>
|
||||
<TextCustom size="small">
|
||||
{formatChatTime(item?.createdAt)}
|
||||
</TextCustom>
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
</BaseBox>
|
||||
))
|
||||
)}
|
||||
</ViewWrapper>
|
||||
|
||||
<DrawerCustom
|
||||
|
||||
@@ -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,222 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
BaseBox,
|
||||
ButtonCenteredOnly,
|
||||
ButtonCustom,
|
||||
Grid,
|
||||
InformationBox,
|
||||
Spacing,
|
||||
StackCustom,
|
||||
TextCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import CopyButton from "@/components/Button/CoyButton";
|
||||
import { MainColor } from "@/constants/color-palet";
|
||||
import DIRECTORY_ID from "@/constants/directory-id";
|
||||
import {
|
||||
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>
|
||||
<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>
|
||||
|
||||
<ButtonCustom
|
||||
disabled={!image}
|
||||
isLoading={isLoading}
|
||||
onPress={() => {
|
||||
handlerUpdateInvoice();
|
||||
}}
|
||||
>
|
||||
Simpan
|
||||
</ButtonCustom>
|
||||
</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,43 @@ 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 { 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";
|
||||
|
||||
export default function InvestmentInputDonation() {
|
||||
const { id } = useLocalSearchParams();
|
||||
const [nominal, setNominal] = useState<number>(0);
|
||||
|
||||
const handlerSubmit = async () => {
|
||||
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 +55,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 +76,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,3 +1,4 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
BackButton,
|
||||
DotButton,
|
||||
@@ -14,16 +15,43 @@ import Donation_ButtonStatusSection from "@/screens/Donation/ButtonStatusSection
|
||||
import Donation_ComponentBoxDetailData from "@/screens/Donation/ComponentBoxDetailData";
|
||||
import Donation_ComponentStoryFunrising from "@/screens/Donation/ComponentStoryFunrising";
|
||||
import Donation_ProgressSection from "@/screens/Donation/ProgressSection";
|
||||
import { apiDonationGetOne } from "@/service/api-client/api-donation";
|
||||
import { FontAwesome6 } from "@expo/vector-icons";
|
||||
import { router, Stack, useLocalSearchParams } from "expo-router";
|
||||
import {
|
||||
router,
|
||||
Stack,
|
||||
useFocusEffect,
|
||||
useLocalSearchParams,
|
||||
} from "expo-router";
|
||||
import _ from "lodash";
|
||||
import { useState } from "react";
|
||||
import { useCallback, useState } from "react";
|
||||
|
||||
export default function DonasiDetailStatus() {
|
||||
const { id, status } = useLocalSearchParams();
|
||||
const [openDrawer, setOpenDrawer] = useState(false);
|
||||
const [openDrawerPublish, setOpenDrawerPublish] = useState(false);
|
||||
|
||||
const [data, setData] = useState<any>();
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
onLoadData();
|
||||
}, [id])
|
||||
);
|
||||
|
||||
const onLoadData = async () => {
|
||||
try {
|
||||
const response = await apiDonationGetOne({
|
||||
id: id as string,
|
||||
category: "permanent",
|
||||
});
|
||||
|
||||
setData(response.data);
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
}
|
||||
};
|
||||
|
||||
const handlePress = (item: IMenuDrawerItem) => {
|
||||
console.log("PATH ", item.path);
|
||||
router.navigate(item.path as any);
|
||||
@@ -46,13 +74,22 @@ export default function DonasiDetailStatus() {
|
||||
/>
|
||||
<ViewWrapper>
|
||||
<Donation_ComponentBoxDetailData
|
||||
data={data}
|
||||
bottomSection={
|
||||
status === "publish" && <Donation_ProgressSection id={id as string} />
|
||||
status === "publish" && (
|
||||
<Donation_ProgressSection id={id as string} />
|
||||
)
|
||||
}
|
||||
/>
|
||||
<Donation_ComponentStoryFunrising id={id as string} />
|
||||
<Donation_ComponentStoryFunrising
|
||||
id={id as string}
|
||||
dataStory={data?.CeritaDonasi}
|
||||
/>
|
||||
<Spacing />
|
||||
<Donation_ButtonStatusSection status={status as string} />
|
||||
<Donation_ButtonStatusSection
|
||||
id={id as string}
|
||||
status={status as string}
|
||||
/>
|
||||
<Spacing />
|
||||
</ViewWrapper>
|
||||
|
||||
|
||||
@@ -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,78 +1,275 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
ButtonCenteredOnly,
|
||||
ButtonCustom,
|
||||
InformationBox,
|
||||
LandscapeFrameUploaded,
|
||||
SelectCustom,
|
||||
Spacing,
|
||||
StackCustom,
|
||||
TextInputCustom,
|
||||
ViewWrapper,
|
||||
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 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." />
|
||||
<InformationBox text="Lengkapi semua data di bawah untuk selanjutnya mengisi cerita penggalangan dana." />
|
||||
{!data || loadList ? (
|
||||
<LoaderCustom />
|
||||
) : (
|
||||
<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 />
|
||||
<ButtonCustom
|
||||
isLoading={isLoading}
|
||||
onPress={() => {
|
||||
handlerSubmitUpdate();
|
||||
}}
|
||||
>
|
||||
Update
|
||||
</ButtonCustom>
|
||||
</StackCustom>
|
||||
)}
|
||||
<Spacing />
|
||||
</ViewWrapper>
|
||||
);
|
||||
|
||||
@@ -1,17 +1,71 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
BaseBox,
|
||||
ButtonCenteredOnly,
|
||||
Grid,
|
||||
InformationBox,
|
||||
LoaderCustom,
|
||||
StackCustom,
|
||||
TextCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import {
|
||||
apiDonationDisbursementOfFundsListById,
|
||||
apiDonationGetOne,
|
||||
} from "@/service/api-client/api-donation";
|
||||
import { formatCurrencyDisplay } from "@/utils/formatCurrencyDisplay";
|
||||
import dayjs from "dayjs";
|
||||
import { router, useLocalSearchParams } from "expo-router";
|
||||
import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
|
||||
import _ from "lodash";
|
||||
import React, { useState } from "react";
|
||||
|
||||
export default function DonationFundDisbursement() {
|
||||
const { id } = useLocalSearchParams();
|
||||
|
||||
const [data, setData] = useState({
|
||||
totalPencairan: 0,
|
||||
akumulasiPencairan: 0,
|
||||
});
|
||||
|
||||
const [listData, setListData] = React.useState<any[] | null>(null);
|
||||
const [loadData, setLoadData] = React.useState(false);
|
||||
|
||||
useFocusEffect(
|
||||
React.useCallback(() => {
|
||||
onLoadData();
|
||||
}, [id])
|
||||
);
|
||||
|
||||
const onLoadData = async () => {
|
||||
try {
|
||||
setLoadData(true);
|
||||
|
||||
const responseData = await apiDonationGetOne({
|
||||
id: id as string,
|
||||
category: "permanent",
|
||||
});
|
||||
|
||||
if (responseData.success) {
|
||||
setData({
|
||||
totalPencairan: responseData.data.totalPencairan,
|
||||
akumulasiPencairan: responseData.data.akumulasiPencairan,
|
||||
});
|
||||
}
|
||||
|
||||
const responseList = await apiDonationDisbursementOfFundsListById({
|
||||
id: id as string,
|
||||
});
|
||||
|
||||
if (responseList.success) {
|
||||
setListData(responseList.data);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
} finally {
|
||||
setLoadData(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<ViewWrapper>
|
||||
@@ -20,47 +74,50 @@ export default function DonationFundDisbursement() {
|
||||
<Grid>
|
||||
<Grid.Col span={6}>
|
||||
<TextCustom bold color="yellow">
|
||||
Rp. 0
|
||||
Rp. {formatCurrencyDisplay(data?.totalPencairan)}
|
||||
</TextCustom>
|
||||
<TextCustom size="small">Total Pencairan Dana</TextCustom>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={6}>
|
||||
<TextCustom bold color="yellow">
|
||||
0 kali
|
||||
{data?.akumulasiPencairan} 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>
|
||||
))}
|
||||
{loadData ? (
|
||||
<LoaderCustom />
|
||||
) : _.isEmpty(listData) ? (
|
||||
<TextCustom align="center" color="gray">
|
||||
Belum ada data
|
||||
</TextCustom>
|
||||
) : (
|
||||
listData?.map((item, index) => (
|
||||
<BaseBox key={index}>
|
||||
<StackCustom>
|
||||
<Grid>
|
||||
<Grid.Col span={8}>
|
||||
<TextCustom bold>{item?.title}</TextCustom>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={4} style={{ alignItems: "flex-end" }}>
|
||||
<TextCustom>{dayjs(item?.createdAt).format("DD MMM YYYY")}</TextCustom>
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
<TextCustom>{item?.deskripsi}</TextCustom>
|
||||
<ButtonCenteredOnly
|
||||
onPress={() => {
|
||||
router.navigate(`/(application)/(image)/preview-image/${item?.imageId}`);
|
||||
}}
|
||||
icon="file-text"
|
||||
>
|
||||
Bukti Transaksi
|
||||
</ButtonCenteredOnly>
|
||||
</StackCustom>
|
||||
</BaseBox>
|
||||
))
|
||||
)}
|
||||
</ViewWrapper>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
BackButton,
|
||||
BoxButtonOnFooter,
|
||||
@@ -9,26 +10,75 @@ import {
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import { IconNews } from "@/components/_Icon";
|
||||
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}
|
||||
onPress={() => router.navigate(`/donation/${id}/(transaction-flow)`)}
|
||||
>
|
||||
Donasi
|
||||
{value?.reminder ? "Waktu berakhir" : "Donasi"}
|
||||
</ButtonCustom>
|
||||
</BoxButtonOnFooter>
|
||||
</>
|
||||
@@ -40,16 +90,25 @@ 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} />}
|
||||
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}
|
||||
/>
|
||||
<Donation_ComponentInfoFundrising id={id as string} />
|
||||
<Donation_ComponentStoryFunrising id={id as string} />
|
||||
</StackCustom>
|
||||
</ViewWrapper>
|
||||
|
||||
|
||||
@@ -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,46 +1,93 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
BaseBox,
|
||||
Grid,
|
||||
LoaderCustom,
|
||||
Spacing,
|
||||
StackCustom,
|
||||
TextCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import { MainColor } from "@/constants/color-palet";
|
||||
import { apiAdminDonationListOfDonaturById } from "@/service/api-admin/api-admin-donation";
|
||||
import { formatCurrencyDisplay } from "@/utils/formatCurrencyDisplay";
|
||||
import { FontAwesome6 } from "@expo/vector-icons";
|
||||
import dayjs from "dayjs";
|
||||
import { useFocusEffect, useLocalSearchParams } from "expo-router";
|
||||
import _ from "lodash";
|
||||
import { useCallback, useState } from "react";
|
||||
|
||||
export default function Donation_ListOfDonatur() {
|
||||
const { id } = useLocalSearchParams();
|
||||
const [listData, setListData] = useState<any[] | null>(null);
|
||||
const [loadData, setLoadData] = useState(false);
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
onLoadData();
|
||||
}, [id])
|
||||
);
|
||||
|
||||
const onLoadData = async () => {
|
||||
try {
|
||||
setLoadData(true);
|
||||
const response = await apiAdminDonationListOfDonaturById({
|
||||
id: id as string,
|
||||
});
|
||||
|
||||
|
||||
if (response.success) {
|
||||
setListData(response.data);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
} finally {
|
||||
setLoadData(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<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"}>
|
||||
{loadData ? (
|
||||
<LoaderCustom />
|
||||
) : _.isEmpty(listData) ? (
|
||||
<TextCustom bold align="center">
|
||||
Belum ada donatur
|
||||
</TextCustom>
|
||||
) : (
|
||||
listData?.map((item: any, index: number) => (
|
||||
<BaseBox key={index}>
|
||||
<Grid>
|
||||
<Grid.Col
|
||||
span={3}
|
||||
style={{ alignItems: "center", justifyContent: "center" }}
|
||||
>
|
||||
<FontAwesome6
|
||||
name="face-smile-wink"
|
||||
size={50}
|
||||
style={{ color: MainColor.yellow }}
|
||||
/>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={9}>
|
||||
<TextCustom bold size="large">
|
||||
Username
|
||||
{item?.Author?.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>
|
||||
))}
|
||||
<Spacing/>
|
||||
<StackCustom gap={"xs"}>
|
||||
<TextCustom size={"small"}>Berdonas sebesar </TextCustom>
|
||||
<TextCustom bold size="large" color="yellow">
|
||||
Rp. {formatCurrencyDisplay(item?.nominal)}
|
||||
</TextCustom>
|
||||
<TextCustom>
|
||||
{dayjs(item?.createdAt).format("DD MMM YYYY, HH:mm")}
|
||||
</TextCustom>
|
||||
</StackCustom>
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
</BaseBox>
|
||||
))
|
||||
)}
|
||||
</ViewWrapper>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
ButtonCenteredOnly,
|
||||
ButtonCustom,
|
||||
@@ -9,9 +10,107 @@ import {
|
||||
TextInputCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import { router } from "expo-router";
|
||||
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");
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<ViewWrapper>
|
||||
<StackCustom gap={"xs"}>
|
||||
@@ -22,6 +121,8 @@ export default function DonationCreateStory() {
|
||||
required
|
||||
showCount
|
||||
maxLength={1000}
|
||||
value={data.pembukaan}
|
||||
onChangeText={(value) => setData({ ...data, pembukaan: value })}
|
||||
/>
|
||||
<TextAreaCustom
|
||||
label="Tujuan Donasi"
|
||||
@@ -29,12 +130,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,17 +155,23 @@ export default function DonationCreateStory() {
|
||||
label="Nama Bank"
|
||||
placeholder="Masukkan nama bank"
|
||||
required
|
||||
value={data.namaBank}
|
||||
onChangeText={(value) => setData({ ...data, namaBank: value })}
|
||||
/>
|
||||
<TextInputCustom
|
||||
label="Nomor Rekening"
|
||||
placeholder="Masukkan nomor rekening"
|
||||
required
|
||||
keyboardType="numeric"
|
||||
value={data.rekening}
|
||||
onChangeText={(value) => setData({ ...data, rekening: value })}
|
||||
/>
|
||||
|
||||
<Spacing />
|
||||
<ButtonCustom
|
||||
isLoading={isLoading}
|
||||
onPress={() => {
|
||||
router.replace(`/donation/(tabs)/status`);
|
||||
handlerSubmit();
|
||||
}}
|
||||
>
|
||||
Simpan
|
||||
|
||||
@@ -3,17 +3,127 @@ import {
|
||||
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 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>
|
||||
<StackCustom gap={"xs"}>
|
||||
@@ -23,18 +133,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,31 +162,52 @@ 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 })}
|
||||
/>
|
||||
)}
|
||||
|
||||
{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 })}
|
||||
/>
|
||||
)}
|
||||
|
||||
<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
|
||||
isLoading={isLoading}
|
||||
onPress={() => {
|
||||
router.replace("/donation/create-story");
|
||||
handlerSubmit();
|
||||
// router.push(`/donation/create-story?id=${"dasdsadsa"}`);
|
||||
}}
|
||||
>
|
||||
Selanjutnya
|
||||
|
||||
@@ -44,6 +44,8 @@ export default function EventBeranda() {
|
||||
<TextCustom align="center">Belum ada event</TextCustom>
|
||||
) : (
|
||||
listData.map((item: any, index) => (
|
||||
|
||||
|
||||
<Event_BoxPublishSection
|
||||
key={index}
|
||||
href={`/event/${item.id}/publish`}
|
||||
|
||||
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>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -11,29 +11,30 @@ import {
|
||||
apiEventGetOne,
|
||||
apiEventListOfParticipants,
|
||||
} from "@/service/api-client/api-event";
|
||||
import { useLocalSearchParams } from "expo-router";
|
||||
import { useEffect, useState } from "react";
|
||||
import dayjs, { Dayjs } from "dayjs";
|
||||
import { useFocusEffect, useLocalSearchParams } from "expo-router";
|
||||
import _ from "lodash";
|
||||
import { useCallback, useState } from "react";
|
||||
import { View } from "react-native";
|
||||
|
||||
export default function EventListOfParticipants() {
|
||||
const { id } = useLocalSearchParams();
|
||||
const [startDate, setStartDate] = useState();
|
||||
const [listData, setListData] = useState([]);
|
||||
const [isLoadData, setIsLoadData] = useState(false);
|
||||
const [startDate, setStartDate] = useState<Dayjs | undefined>();
|
||||
const [listData, setListData] = useState<any[] | null>(null);
|
||||
const [loadtData, setLoadData] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
handlerLoadData();
|
||||
}, [id]);
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
handlerLoadData();
|
||||
}, [id])
|
||||
);
|
||||
|
||||
const handlerLoadData = () => {
|
||||
try {
|
||||
setIsLoadData(true);
|
||||
onLoadData();
|
||||
onLoadList();
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
} finally {
|
||||
setIsLoadData(false);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -41,7 +42,8 @@ export default function EventListOfParticipants() {
|
||||
try {
|
||||
const response = await apiEventGetOne({ id: id as string });
|
||||
if (response.success) {
|
||||
setStartDate(response.data.tanggal);
|
||||
const date = dayjs(response.data.tanggal);
|
||||
setStartDate(date);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
@@ -50,30 +52,36 @@ export default function EventListOfParticipants() {
|
||||
|
||||
const onLoadList = async () => {
|
||||
try {
|
||||
setLoadData(true);
|
||||
const response = await apiEventListOfParticipants({ id: id as string });
|
||||
|
||||
if (response.success) {
|
||||
setListData(response.data);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
} finally {
|
||||
setLoadData(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<ViewWrapper>
|
||||
{isLoadData ? (
|
||||
{loadtData && !listData ? (
|
||||
<LoaderCustom />
|
||||
) : listData.length === 0 ? (
|
||||
<TextCustom align="center">Belum ada peserta</TextCustom>
|
||||
) : _.isEmpty(listData) ? (
|
||||
<TextCustom align="center" color="gray">
|
||||
Belum ada peserta
|
||||
</TextCustom>
|
||||
) : (
|
||||
listData.map((item: any, index: number) => (
|
||||
listData?.map((item: any, index: number) => (
|
||||
<BaseBox key={index}>
|
||||
<AvatarUsernameAndOtherComponent
|
||||
avatar={item?.User?.Profile?.imageId}
|
||||
name={item?.User?.username}
|
||||
avatarHref={`/profile/${item?.User?.Profile?.id}`}
|
||||
rightComponent={
|
||||
new Date().getTime() > new Date(startDate as any).getTime() ? (
|
||||
startDate && startDate.subtract(1, "hour").diff(dayjs()) < 0 ? (
|
||||
<View
|
||||
style={{
|
||||
justifyContent: "flex-end",
|
||||
|
||||
@@ -55,6 +55,8 @@ export default function EventDetailPublish() {
|
||||
userId: user?.id as string,
|
||||
});
|
||||
|
||||
console.log("[RES CHECK PARTICIPANTS]", responseCheckParticipants);
|
||||
|
||||
if (
|
||||
responseCheckParticipants.success &&
|
||||
responseCheckParticipants.data
|
||||
@@ -69,6 +71,8 @@ export default function EventDetailPublish() {
|
||||
}
|
||||
}
|
||||
|
||||
console.log("[participans]", isParticipant);
|
||||
|
||||
const handlePress = (item: IMenuDrawerItem) => {
|
||||
console.log("PATH ", item.path);
|
||||
router.navigate(item.path as any);
|
||||
|
||||
@@ -191,7 +191,7 @@ export default function EventCreate() {
|
||||
placeholder="Masukkan deskripsi event"
|
||||
required
|
||||
showCount
|
||||
maxLength={100}
|
||||
maxLength={1000}
|
||||
onChangeText={(value: any) =>
|
||||
setData({ ...data, deskripsi: value })
|
||||
}
|
||||
|
||||
@@ -1,25 +1,22 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
AlertCustom,
|
||||
AvatarComp,
|
||||
AvatarCustom,
|
||||
ButtonCustom,
|
||||
CenterCustom,
|
||||
DrawerCustom,
|
||||
FloatingButton,
|
||||
Grid,
|
||||
LoaderCustom,
|
||||
StackCustom,
|
||||
TextCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import { MainColor } from "@/constants/color-palet";
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
import Forum_BoxDetailSection from "@/screens/Forum/DiscussionBoxSection";
|
||||
import { listDummyDiscussionForum } from "@/screens/Forum/list-data-dummy";
|
||||
import Forum_MenuDrawerBerandaSection from "@/screens/Forum/MenuDrawerSection.tsx/MenuBeranda";
|
||||
import { apiForumGetAll } from "@/service/api-client/api-forum";
|
||||
import { apiUser } from "@/service/api-client/api-user";
|
||||
import { useFocusEffect, useLocalSearchParams } from "expo-router";
|
||||
import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
|
||||
import _ from "lodash";
|
||||
import { useCallback, useState } from "react";
|
||||
|
||||
@@ -28,9 +25,6 @@ export default function Forumku() {
|
||||
const { user } = useAuth();
|
||||
const [openDrawer, setOpenDrawer] = useState(false);
|
||||
const [status, setStatus] = useState("");
|
||||
const [alertStatus, setAlertStatus] = useState(false);
|
||||
const [deleteAlert, setDeleteAlert] = useState(false);
|
||||
|
||||
const [listData, setListData] = useState<any | null>(null);
|
||||
const [dataUser, setDataUser] = useState<any | null>(null);
|
||||
const [loadingGetList, setLoadingGetList] = useState(false);
|
||||
@@ -38,8 +32,8 @@ export default function Forumku() {
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
onLoadData();
|
||||
onLoadDataProfile(user?.id as string);
|
||||
}, [user?.id])
|
||||
onLoadDataProfile(id as string);
|
||||
}, [id])
|
||||
);
|
||||
|
||||
const onLoadDataProfile = async (id: string) => {
|
||||
@@ -71,7 +65,17 @@ export default function Forumku() {
|
||||
|
||||
return (
|
||||
<>
|
||||
<ViewWrapper>
|
||||
<ViewWrapper
|
||||
floatingButton={
|
||||
user?.id === id && (
|
||||
<FloatingButton
|
||||
onPress={() =>
|
||||
router.navigate("/(application)/(user)/forum/create")
|
||||
}
|
||||
/>
|
||||
)
|
||||
}
|
||||
>
|
||||
<StackCustom>
|
||||
<CenterCustom>
|
||||
<AvatarComp
|
||||
|
||||
@@ -1,19 +1,40 @@
|
||||
import {
|
||||
BaseBox,
|
||||
FloatingButton,
|
||||
Grid,
|
||||
ProgressCustom,
|
||||
StackCustom,
|
||||
TextCustom,
|
||||
ViewWrapper,
|
||||
LoaderCustom,
|
||||
ViewWrapper
|
||||
} from "@/components";
|
||||
import DUMMY_IMAGE from "@/constants/dummy-image-value";
|
||||
import dayjs from "dayjs";
|
||||
import { Image } from "expo-image";
|
||||
import { router } from "expo-router";
|
||||
import { View } from "react-native";
|
||||
import NoDataText from "@/components/_ShareComponent/NoDataText";
|
||||
import Investment_BoxBerandaSection from "@/screens/Invesment/BoxBerandaSection";
|
||||
import { apiInvestmentGetAll } from "@/service/api-client/api-investment";
|
||||
import { router, useFocusEffect } from "expo-router";
|
||||
import _ from "lodash";
|
||||
import { useCallback, useState } from "react";
|
||||
|
||||
export default function InvestmentBursa() {
|
||||
const [list, setList] = useState<any[] | null>(null);
|
||||
const [loadingList, setLoadingList] = useState(false);
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
onLoadList();
|
||||
}, [])
|
||||
);
|
||||
|
||||
const onLoadList = async () => {
|
||||
try {
|
||||
setLoadingList(true);
|
||||
const response = await apiInvestmentGetAll({
|
||||
category: "bursa"
|
||||
});
|
||||
// console.log("[DATA LIST]", JSON.stringify(response.data, null, 2));
|
||||
setList(response.data);
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
} finally {
|
||||
setLoadingList(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<ViewWrapper
|
||||
hideFooter
|
||||
@@ -21,61 +42,15 @@ export default function InvestmentBursa() {
|
||||
<FloatingButton onPress={() => router.push("/investment/create")} />
|
||||
}
|
||||
>
|
||||
{Array.from({ length: 10 }).map((_, index) => (
|
||||
<BaseBox key={index} paddingTop={7} paddingBottom={7} href={`/investment/${index}`}>
|
||||
<Grid>
|
||||
<Grid.Col span={5}>
|
||||
<Image
|
||||
source={DUMMY_IMAGE.background}
|
||||
style={{ width: "auto", height: 100, borderRadius: 10 }}
|
||||
/>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={1}>
|
||||
<View />
|
||||
</Grid.Col>
|
||||
<Grid.Col span={6}>
|
||||
<StackCustom>
|
||||
<TextCustom truncate={2}>
|
||||
Title here : Lorem ipsum dolor sit amet consectetur
|
||||
adipisicing elit. Omnis, exercitationem, sequi enim quod
|
||||
distinctio maiores laudantium amet, quidem atque repellat sit
|
||||
vitae qui aliquam est veritatis laborum eum voluptatum totam!
|
||||
</TextCustom>
|
||||
<ProgressCustom value={index % 5 * 20} size="lg" />
|
||||
<TextCustom>
|
||||
Sisa waktu: {dayjs().diff(dayjs(), "day")} hari
|
||||
</TextCustom>
|
||||
</StackCustom>
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
</BaseBox>
|
||||
))}
|
||||
{loadingList ? (
|
||||
<LoaderCustom />
|
||||
) : _.isEmpty(list) ? (
|
||||
<NoDataText />
|
||||
) : (
|
||||
list?.map((item: any, index: number) => (
|
||||
<Investment_BoxBerandaSection id={item.id} data={item} key={index} />
|
||||
))
|
||||
)}
|
||||
</ViewWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
// <View style={{ padding: 20, gap: 16 }}>
|
||||
// <TextCustom>Progress 70%</TextCustom>
|
||||
// <ProgressCustom value={70} color="primary" size="md" />
|
||||
|
||||
// <TextCustom>Success Progress</TextCustom>
|
||||
// <ProgressCustom value={40} color="success" size="lg" />
|
||||
|
||||
// <TextCustom>Warning Progress (small)</TextCustom>
|
||||
// <ProgressCustom value={90} color="warning" size="sm" />
|
||||
|
||||
// <TextCustom>Error Indeterminate</TextCustom>
|
||||
// <ProgressCustom value={null} color="error" size="md" />
|
||||
|
||||
// <TextCustom>Custom Radius</TextCustom>
|
||||
// <ProgressCustom value={60} color="info" size="xl" radius={4} />
|
||||
|
||||
// <ProgressCustom value={70} color="primary" size="lg" />
|
||||
|
||||
// <ProgressCustom value={45} color="success" size="md" label="Halfway!" />
|
||||
|
||||
// <ProgressCustom value={90} color="warning" size="lg" showLabel={false} />
|
||||
|
||||
// <ProgressCustom value={null} color="error" size="sm" label="Loading..." />
|
||||
// </View>;
|
||||
@@ -1,50 +1,102 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
BaseBox,
|
||||
Grid,
|
||||
LoaderCustom,
|
||||
ProgressCustom,
|
||||
Spacing,
|
||||
StackCustom,
|
||||
TextCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import { router } from "expo-router";
|
||||
import NoDataText from "@/components/_ShareComponent/NoDataText";
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
import {
|
||||
apiInvestmentGetAll
|
||||
} from "@/service/api-client/api-investment";
|
||||
import { formatCurrencyDisplay } from "@/utils/formatCurrencyDisplay";
|
||||
import { router, useFocusEffect } from "expo-router";
|
||||
import _ from "lodash";
|
||||
import React, { useCallback, useState } from "react";
|
||||
import { View } from "react-native";
|
||||
|
||||
export default function InvestmentMyHolding() {
|
||||
const { user } = useAuth();
|
||||
const [list, setList] = useState<any[] | null>(null);
|
||||
const [loadingList, setLoadingList] = useState(false);
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
onLoadList();
|
||||
}, [user?.id])
|
||||
);
|
||||
|
||||
const onLoadList = async () => {
|
||||
try {
|
||||
setLoadingList(true);
|
||||
const response = await apiInvestmentGetAll({
|
||||
category: "my-holding",
|
||||
authorId: user?.id,
|
||||
});
|
||||
console.log("[DATA LIST]", JSON.stringify(response.data, null, 2));
|
||||
setList(response.data);
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
} finally {
|
||||
setLoadingList(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<ViewWrapper hideFooter>
|
||||
{Array.from({ length: 10 }).map((_, index) => (
|
||||
<BaseBox key={index} paddingTop={7} paddingBottom={7} onPress={() => router.push(`/investment/${index}/(my-holding)/holding-${index}`)}>
|
||||
<Grid>
|
||||
<Grid.Col span={6}>
|
||||
<StackCustom gap={"xs"}>
|
||||
<TextCustom truncate={2}>
|
||||
Title here : Lorem ipsum dolor sit amet consectetur
|
||||
adipisicing elit. Omnis, exercitationem, sequi enim quod
|
||||
distinctio maiores laudantium amet, quidem atque repellat sit
|
||||
vitae qui aliquam est veritatis laborum eum voluptatum totam!
|
||||
</TextCustom>
|
||||
{loadingList ? (
|
||||
<LoaderCustom />
|
||||
) : _.isEmpty(list) ? (
|
||||
<NoDataText />
|
||||
) : (
|
||||
list?.map((item, index) => (
|
||||
<BaseBox
|
||||
key={index}
|
||||
paddingTop={7}
|
||||
paddingBottom={7}
|
||||
onPress={() =>
|
||||
router.push(`/investment/${item?.id}/(my-holding)/${item?.id}`)
|
||||
}
|
||||
>
|
||||
<Grid>
|
||||
<Grid.Col span={6}>
|
||||
<StackCustom gap={"xs"}>
|
||||
<TextCustom truncate={2}>{item?.title}</TextCustom>
|
||||
|
||||
<Spacing height={5} />
|
||||
<TextCustom size="small">Rp. 7.500.000</TextCustom>
|
||||
<TextCustom size="small">300 Lembar</TextCustom>
|
||||
</StackCustom>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={1}>
|
||||
<View />
|
||||
</Grid.Col>
|
||||
<Grid.Col
|
||||
span={5}
|
||||
style={{
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<ProgressCustom value={(index % 5) * 20} size="lg" />
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
</BaseBox>
|
||||
))}
|
||||
<Spacing height={5} />
|
||||
<TextCustom size="small">
|
||||
Rp. {formatCurrencyDisplay(item?.nominal)}
|
||||
</TextCustom>
|
||||
<TextCustom size="small">
|
||||
{item?.lembarTerbeli} Lembar
|
||||
</TextCustom>
|
||||
</StackCustom>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={1}>
|
||||
<View />
|
||||
</Grid.Col>
|
||||
<Grid.Col
|
||||
span={5}
|
||||
style={{
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<ProgressCustom
|
||||
value={item?.progress}
|
||||
label={`${item?.progress}%`}
|
||||
size="lg"
|
||||
/>
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
</BaseBox>
|
||||
))
|
||||
)}
|
||||
</ViewWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,13 +1,48 @@
|
||||
import { ScrollableCustom, ViewWrapper } from "@/components";
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
LoaderCustom,
|
||||
ScrollableCustom,
|
||||
TextCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
import { dummyMasterStatus } from "@/lib/dummy-data/_master/status";
|
||||
import Investment_StatusBox from "@/screens/Invesment/StatusBox";
|
||||
import { useState } from "react";
|
||||
import { apiInvestmentGetByStatus } from "@/service/api-client/api-investment";
|
||||
import { useFocusEffect } from "expo-router";
|
||||
import _ from "lodash";
|
||||
import { useCallback, useState } from "react";
|
||||
|
||||
export default function InvestmentPortofolio() {
|
||||
const { user } = useAuth();
|
||||
const [activeCategory, setActiveCategory] = useState<string | null>(
|
||||
"publish"
|
||||
);
|
||||
|
||||
const [listData, setListData] = useState<any[]>([]);
|
||||
const [loadingList, setLoadingList] = useState(false);
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
onLoadData();
|
||||
}, [user?.id, activeCategory])
|
||||
);
|
||||
|
||||
const onLoadData = async () => {
|
||||
try {
|
||||
setLoadingList(true);
|
||||
const response = await apiInvestmentGetByStatus({
|
||||
authorId: user?.id as string,
|
||||
status: activeCategory as string,
|
||||
});
|
||||
setListData(response.data);
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
} finally {
|
||||
setLoadingList(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handlePress = (item: any) => {
|
||||
setActiveCategory(item.value);
|
||||
// tambahkan logika lain seperti filter dsb.
|
||||
@@ -26,14 +61,20 @@ export default function InvestmentPortofolio() {
|
||||
);
|
||||
return (
|
||||
<ViewWrapper headerComponent={scrollComponent} hideFooter>
|
||||
{Array.from({ length: 10 }).map((_, index) => (
|
||||
<Investment_StatusBox
|
||||
key={index}
|
||||
id={index.toString()}
|
||||
status={activeCategory as string}
|
||||
href={`/investment/${index}/${activeCategory}/detail`}
|
||||
/>
|
||||
))}
|
||||
{loadingList ? (
|
||||
<LoaderCustom />
|
||||
) : _.isEmpty(listData) ? (
|
||||
<TextCustom align="center">Tidak ada data {activeCategory}</TextCustom>
|
||||
) : (
|
||||
listData.map((item: any, index: number) => (
|
||||
<Investment_StatusBox
|
||||
key={index}
|
||||
data={item}
|
||||
status={activeCategory as string}
|
||||
href={`/investment/${item.id}/${activeCategory}/detail`}
|
||||
/>
|
||||
))
|
||||
)}
|
||||
</ViewWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,81 +1,124 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
BadgeCustom,
|
||||
BaseBox,
|
||||
Grid,
|
||||
LoaderCustom,
|
||||
StackCustom,
|
||||
TextCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import { dummyMasterStatusTransaction } from "@/lib/dummy-data/_master/status-transaction";
|
||||
import NoDataText from "@/components/_ShareComponent/NoDataText";
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
import { apiInvestmentGetInvoice } from "@/service/api-client/api-investment";
|
||||
import { GStyles } from "@/styles/global-styles";
|
||||
import dayjs from "dayjs";
|
||||
import { router } from "expo-router";
|
||||
import { formatChatTime } from "@/utils/formatChatTime";
|
||||
import { formatCurrencyDisplay } from "@/utils/formatCurrencyDisplay";
|
||||
import { router, useFocusEffect } from "expo-router";
|
||||
import _ from "lodash";
|
||||
import { useCallback, useState } from "react";
|
||||
import { View } from "react-native";
|
||||
|
||||
export default function InvestmentTransaction() {
|
||||
const randomStatusData = Array.from({ length: 10 }, () => {
|
||||
const randomIndex = Math.floor(
|
||||
Math.random() * dummyMasterStatusTransaction.length
|
||||
);
|
||||
return dummyMasterStatusTransaction[randomIndex];
|
||||
});
|
||||
const { user } = useAuth();
|
||||
const [list, setList] = useState<any>([]);
|
||||
const [loadList, setLoadList] = useState<boolean>(false);
|
||||
|
||||
const handlePress = (value: string) => {
|
||||
if (value === "menunggu") {
|
||||
router.push(`/investment/${value}/(transaction-flow)/invoice`);
|
||||
} else if (value === "proses") {
|
||||
router.push(`/investment/${value}/(transaction-flow)/process`);
|
||||
} else if (value === "berhasil") {
|
||||
router.push(`/investment/${value}/(transaction-flow)/success`);
|
||||
} else if (value === "gagal") {
|
||||
router.push(`/investment/${value}/(transaction-flow)/failed`);
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
onLoadList();
|
||||
}, [user?.id])
|
||||
);
|
||||
|
||||
const onLoadList = async () => {
|
||||
try {
|
||||
setLoadList(true);
|
||||
const response = await apiInvestmentGetInvoice({
|
||||
authorId: user?.id as string,
|
||||
category: "transaction",
|
||||
});
|
||||
console.log("[RESPONSE LIST]", JSON.stringify(response.data, null, 2));
|
||||
setList(response.data);
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
} finally {
|
||||
setLoadList(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handlerColor = (status: string) => {
|
||||
if (status === "menunggu") {
|
||||
return "orange";
|
||||
} else if (status === "proses") {
|
||||
return "white";
|
||||
} else if (status === "berhasil") {
|
||||
return "green";
|
||||
} else if (status === "gagal") {
|
||||
return "red";
|
||||
}
|
||||
};
|
||||
|
||||
const handlePress = ({ id, status }: { id: string; status: string }) => {
|
||||
if (status === "menunggu") {
|
||||
router.push(`/investment/${id}/(transaction-flow)/invoice`);
|
||||
} else if (status === "proses") {
|
||||
router.push(`/investment/${id}/(transaction-flow)/process`);
|
||||
} else if (status === "berhasil") {
|
||||
router.push(`/investment/${id}/(transaction-flow)/success`);
|
||||
} else if (status === "gagal") {
|
||||
router.push(`/investment/${id}/(transaction-flow)/failed`);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<ViewWrapper hideFooter>
|
||||
{randomStatusData.map((item, i) => (
|
||||
<BaseBox
|
||||
key={i}
|
||||
paddingTop={7}
|
||||
paddingBottom={7}
|
||||
onPress={() => {
|
||||
handlePress(item.value);
|
||||
}}
|
||||
>
|
||||
<Grid>
|
||||
<Grid.Col span={6}>
|
||||
<StackCustom gap={"xs"}>
|
||||
<TextCustom truncate>
|
||||
Title Investment: Lorem ipsum dolor sit amet consectetur
|
||||
adipisicing elit. Am culpa excepturi deleniti soluta animi
|
||||
porro amet ducimus.
|
||||
</TextCustom>
|
||||
<TextCustom color="gray" size="small">
|
||||
{dayjs().format("DD/MM/YYYY")}
|
||||
</TextCustom>
|
||||
</StackCustom>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={1}>
|
||||
<View />
|
||||
</Grid.Col>
|
||||
<Grid.Col span={5} style={{ alignItems: "flex-end" }}>
|
||||
<StackCustom gap={"xs"}>
|
||||
<TextCustom bold truncate>
|
||||
Rp. 7.500.000
|
||||
</TextCustom>
|
||||
<BadgeCustom
|
||||
variant="light"
|
||||
color={item.color}
|
||||
style={GStyles.alignSelfFlexEnd}
|
||||
>
|
||||
{item.label}
|
||||
</BadgeCustom>
|
||||
</StackCustom>
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
</BaseBox>
|
||||
))}
|
||||
{loadList ? (
|
||||
<LoaderCustom />
|
||||
) : _.isEmpty(list) ? (
|
||||
<NoDataText/>
|
||||
) : (
|
||||
list.map((item: any, i: number) => (
|
||||
<BaseBox
|
||||
key={i}
|
||||
paddingTop={7}
|
||||
paddingBottom={7}
|
||||
onPress={() => {
|
||||
handlePress({
|
||||
id: item.id,
|
||||
status: _.lowerCase(item.statusInvoice),
|
||||
});
|
||||
}}
|
||||
>
|
||||
<Grid>
|
||||
<Grid.Col span={6}>
|
||||
<StackCustom gap={"xs"}>
|
||||
<TextCustom truncate>{item?.title || "-"}</TextCustom>
|
||||
<TextCustom color="gray" size="small">
|
||||
{formatChatTime(item?.createdAt)}
|
||||
</TextCustom>
|
||||
</StackCustom>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={1}>
|
||||
<View />
|
||||
</Grid.Col>
|
||||
<Grid.Col span={5} style={{ alignItems: "flex-end" }}>
|
||||
<StackCustom gap={"xs"}>
|
||||
<TextCustom bold truncate>
|
||||
Rp. {formatCurrencyDisplay(item?.nominal) || "-"}
|
||||
</TextCustom>
|
||||
<BadgeCustom
|
||||
variant="light"
|
||||
color={handlerColor(_.lowerCase(item.statusInvoice))}
|
||||
style={GStyles.alignSelfFlexEnd}
|
||||
>
|
||||
{item?.statusInvoice || "-"}
|
||||
</BadgeCustom>
|
||||
</StackCustom>
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
</BaseBox>
|
||||
))
|
||||
)}
|
||||
</ViewWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -7,17 +7,78 @@ import {
|
||||
InformationBox,
|
||||
Spacing,
|
||||
StackCustom,
|
||||
TextCustom,
|
||||
TextInputCustom,
|
||||
ViewWrapper
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import { MainColor } from "@/constants/color-palet";
|
||||
import DIRECTORY_ID from "@/constants/directory-id";
|
||||
import { apiInvestmentUpsertDocument } from "@/service/api-client/api-investment";
|
||||
import { uploadFileService } from "@/service/upload-service";
|
||||
import pickFile from "@/utils/pickFile";
|
||||
import { FontAwesome5 } from "@expo/vector-icons";
|
||||
import { router } from "expo-router";
|
||||
import { router, useLocalSearchParams } from "expo-router";
|
||||
import { useState } from "react";
|
||||
import Toast from "react-native-toast-message";
|
||||
|
||||
export default function InvestmentAddDocument() {
|
||||
const { id } = useLocalSearchParams();
|
||||
const [data, setData] = useState({
|
||||
title: "",
|
||||
});
|
||||
const [pdf, setPdf] = useState<any>(null);
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
|
||||
const handlerSubmit = async () => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
const responseUploadFile = await uploadFileService({
|
||||
dirId: DIRECTORY_ID.investasi_dokumen,
|
||||
imageUri: pdf.uri,
|
||||
});
|
||||
|
||||
if (!responseUploadFile.success) {
|
||||
throw new Error(responseUploadFile.message);
|
||||
}
|
||||
|
||||
const newData = {
|
||||
title: data.title,
|
||||
fileId: responseUploadFile.data.id,
|
||||
};
|
||||
|
||||
const response = await apiInvestmentUpsertDocument({
|
||||
id: id as string,
|
||||
data: newData,
|
||||
});
|
||||
|
||||
if (response.success) {
|
||||
Toast.show({
|
||||
type: "success",
|
||||
text1: "Data berhasil disimpan",
|
||||
});
|
||||
|
||||
router.back();
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
Toast.show({
|
||||
type: "error",
|
||||
text1: "Gagal menyimpan data",
|
||||
});
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const buttonFooter = (
|
||||
<BoxButtonOnFooter>
|
||||
<ButtonCustom onPress={() => router.back()}>Simpan</ButtonCustom>
|
||||
<ButtonCustom
|
||||
isLoading={isLoading}
|
||||
disabled={!pdf || data.title.length <= 0}
|
||||
onPress={handlerSubmit}
|
||||
>
|
||||
Simpan
|
||||
</ButtonCustom>
|
||||
</BoxButtonOnFooter>
|
||||
);
|
||||
|
||||
@@ -31,22 +92,33 @@ export default function InvestmentAddDocument() {
|
||||
label="Judul Dokumen"
|
||||
placeholder="Masukan judul dokumen"
|
||||
required
|
||||
value={data.title}
|
||||
onChangeText={(value) => setData({ ...data, title: value })}
|
||||
/>
|
||||
|
||||
<BaseBox>
|
||||
<CenterCustom>
|
||||
<FontAwesome5
|
||||
name="file-pdf"
|
||||
size={30}
|
||||
color={MainColor.disabled}
|
||||
/>
|
||||
{pdf ? (
|
||||
<TextCustom truncate>{pdf.name}</TextCustom>
|
||||
) : (
|
||||
<FontAwesome5
|
||||
name="file-pdf"
|
||||
size={30}
|
||||
color={MainColor.disabled}
|
||||
/>
|
||||
)}
|
||||
</CenterCustom>
|
||||
</BaseBox>
|
||||
|
||||
<ButtonCenteredOnly
|
||||
icon="upload"
|
||||
onPress={() =>
|
||||
router.push("/(application)/(image)/take-picture/123")
|
||||
pickFile({
|
||||
allowedType: "pdf",
|
||||
setPdfUri(file) {
|
||||
setPdf(file);
|
||||
},
|
||||
})
|
||||
}
|
||||
>
|
||||
Upload
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
BaseBox,
|
||||
BoxButtonOnFooter,
|
||||
@@ -7,17 +8,99 @@ import {
|
||||
InformationBox,
|
||||
Spacing,
|
||||
StackCustom,
|
||||
TextCustom,
|
||||
TextInputCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import { MainColor } from "@/constants/color-palet";
|
||||
import { FontAwesome5 } from "@expo/vector-icons";
|
||||
import { router } from "expo-router";
|
||||
import DIRECTORY_ID from "@/constants/directory-id";
|
||||
import {
|
||||
apiInvestmentGetDocument,
|
||||
apiInvestmentUpsertDocument,
|
||||
} from "@/service/api-client/api-investment";
|
||||
import { deleteFileService, 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 InvestmentEditDocument() {
|
||||
const { id } = useLocalSearchParams();
|
||||
const [data, setData] = useState<any>(null);
|
||||
const [pdf, setPdf] = useState<any>(null);
|
||||
const [titleFile, setTitleFile] = useState<string>("");
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
|
||||
useEffect(() => {
|
||||
onLoadData();
|
||||
}, [id]);
|
||||
|
||||
const onLoadData = async () => {
|
||||
try {
|
||||
const response = await apiInvestmentGetDocument({
|
||||
id: id as string,
|
||||
category: "one-document",
|
||||
});
|
||||
setData(response.data);
|
||||
setTitleFile(response.data.title);
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
}
|
||||
};
|
||||
|
||||
const handlerUpdate = async () => {
|
||||
const prevFileId = data.fileId;
|
||||
try {
|
||||
setIsLoading(true);
|
||||
const responseUploadFile = await uploadFileService({
|
||||
dirId: DIRECTORY_ID.investasi_dokumen,
|
||||
imageUri: pdf.uri,
|
||||
});
|
||||
|
||||
if (!responseUploadFile.success) {
|
||||
throw new Error(responseUploadFile.message);
|
||||
}
|
||||
|
||||
const newData = {
|
||||
title: data.title,
|
||||
fileId: responseUploadFile.data.id,
|
||||
};
|
||||
|
||||
const response = await apiInvestmentUpsertDocument({
|
||||
id: id as string,
|
||||
data: newData,
|
||||
});
|
||||
|
||||
if (response.success) {
|
||||
const delPrevFile = await deleteFileService({
|
||||
id: prevFileId,
|
||||
});
|
||||
|
||||
console.log("[DEL PREV FILE]", delPrevFile);
|
||||
|
||||
Toast.show({
|
||||
type: "success",
|
||||
text1: "Data berhasil diupdate",
|
||||
});
|
||||
|
||||
router.back();
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
Toast.show({
|
||||
type: "error",
|
||||
text1: "Gagal mengupdate data",
|
||||
});
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const buttonFooter = (
|
||||
<BoxButtonOnFooter>
|
||||
<ButtonCustom onPress={() => router.back()}>Update</ButtonCustom>
|
||||
<ButtonCustom isLoading={isLoading} disabled={!pdf} onPress={handlerUpdate}>
|
||||
Update
|
||||
</ButtonCustom>
|
||||
</BoxButtonOnFooter>
|
||||
);
|
||||
|
||||
@@ -31,22 +114,29 @@ export default function InvestmentEditDocument() {
|
||||
label="Judul Dokumen"
|
||||
placeholder="Masukan judul dokumen"
|
||||
required
|
||||
value={data?.title}
|
||||
onChangeText={(value) => setData({ ...data, title: value })}
|
||||
/>
|
||||
|
||||
<BaseBox>
|
||||
<CenterCustom>
|
||||
<FontAwesome5
|
||||
name="file-pdf"
|
||||
size={30}
|
||||
color={MainColor.disabled}
|
||||
/>
|
||||
{pdf ? (
|
||||
<TextCustom truncate>{pdf.name}</TextCustom>
|
||||
) : (
|
||||
<TextCustom truncate>{_.snakeCase(titleFile || "")}.pdf</TextCustom>
|
||||
)}
|
||||
</CenterCustom>
|
||||
</BaseBox>
|
||||
|
||||
<ButtonCenteredOnly
|
||||
icon="upload"
|
||||
onPress={() =>
|
||||
router.push("/(application)/(image)/take-picture/123")
|
||||
pickFile({
|
||||
allowedType: "pdf",
|
||||
setPdfUri(file) {
|
||||
setPdf(file);
|
||||
},
|
||||
})
|
||||
}
|
||||
>
|
||||
Upload
|
||||
|
||||
@@ -1,16 +1,58 @@
|
||||
import { ViewWrapper } from "@/components";
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import { LoaderCustom, TextCustom, ViewWrapper } from "@/components";
|
||||
import Investment_BoxDetailDocument from "@/screens/Invesment/Document/RecapBoxDetail";
|
||||
import { apiInvestmentGetDocument } from "@/service/api-client/api-investment";
|
||||
import { useFocusEffect, useLocalSearchParams } from "expo-router";
|
||||
import _ from "lodash";
|
||||
import { useCallback, useState } from "react";
|
||||
|
||||
export default function InvestmentListOfDocument() {
|
||||
const { id } = useLocalSearchParams();
|
||||
console.log("ID >> ", id);
|
||||
|
||||
const [list, setList] = useState<any[] | null>(null);
|
||||
const [loadList, setLoadList] = useState(false);
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
onLoadListDocument();
|
||||
}, [id])
|
||||
);
|
||||
|
||||
const onLoadListDocument = async () => {
|
||||
try {
|
||||
setLoadList(true);
|
||||
const response = await apiInvestmentGetDocument({
|
||||
id: id as string,
|
||||
category: "all-document",
|
||||
});
|
||||
|
||||
setList(response.data);
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
setList([]);
|
||||
} finally {
|
||||
setLoadList(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<ViewWrapper>
|
||||
{Array.from({ length: 10 }).map((_, index) => (
|
||||
<Investment_BoxDetailDocument
|
||||
key={index}
|
||||
title={`Judul Dokumen ${index + 1}`}
|
||||
href={`/(file)/${index + 1}`}
|
||||
/>
|
||||
))}
|
||||
{loadList ? (
|
||||
<LoaderCustom />
|
||||
) : _.isEmpty(list) ? (
|
||||
<TextCustom align="center" color="gray">
|
||||
Tidak ada data
|
||||
</TextCustom>
|
||||
) : (
|
||||
list?.map((item: any, index: number) => (
|
||||
<Investment_BoxDetailDocument
|
||||
key={index}
|
||||
title={item.title}
|
||||
href={`/(file)/${item.fileId}`}
|
||||
/>
|
||||
))
|
||||
)}
|
||||
</ViewWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,23 +1,90 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
AlertDefaultSystem,
|
||||
BackButton,
|
||||
DotButton,
|
||||
DrawerCustom,
|
||||
LoaderCustom,
|
||||
MenuDrawerDynamicGrid,
|
||||
TextCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import { IconEdit } from "@/components/_Icon";
|
||||
import { MainColor } from "@/constants/color-palet";
|
||||
import { ICON_SIZE_SMALL } from "@/constants/constans-value";
|
||||
import Investment_BoxDetailDocument from "@/screens/Invesment/Document/RecapBoxDetail";
|
||||
import {
|
||||
apiInvestmentDeleteDocument,
|
||||
apiInvestmentGetDocument,
|
||||
} from "@/service/api-client/api-investment";
|
||||
import { AntDesign, Ionicons } from "@expo/vector-icons";
|
||||
import { router, Stack, useLocalSearchParams } from "expo-router";
|
||||
import { useState } from "react";
|
||||
import {
|
||||
router,
|
||||
Stack,
|
||||
useFocusEffect,
|
||||
useLocalSearchParams,
|
||||
} from "expo-router";
|
||||
import _ from "lodash";
|
||||
import { useCallback, useState } from "react";
|
||||
import Toast from "react-native-toast-message";
|
||||
|
||||
export default function InvestmentRecapOfDocument() {
|
||||
const { id } = useLocalSearchParams();
|
||||
const [openDrawer, setOpenDrawer] = useState(false);
|
||||
const [openDrawerBox, setOpenDrawerBox] = useState(false);
|
||||
const [list, setList] = useState<any[] | null>(null);
|
||||
const [loadList, setLoadList] = useState(false);
|
||||
const [selectId, setSelectId] = useState<string | null>(null);
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
onLoadListDocument();
|
||||
}, [id])
|
||||
);
|
||||
|
||||
const onLoadListDocument = async () => {
|
||||
try {
|
||||
setLoadList(true);
|
||||
const response = await apiInvestmentGetDocument({
|
||||
id: id as string,
|
||||
category: "all-document",
|
||||
});
|
||||
|
||||
setList(response.data);
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
setList([]);
|
||||
} finally {
|
||||
setLoadList(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handlerDeleteDocument = async () => {
|
||||
try {
|
||||
const response = await apiInvestmentDeleteDocument({
|
||||
id: selectId as string,
|
||||
});
|
||||
|
||||
if (response.success) {
|
||||
Toast.show({
|
||||
type: "success",
|
||||
text1: "Data berhasil dihapus",
|
||||
});
|
||||
setList((prev: any[] | null) => {
|
||||
if (!prev) return null;
|
||||
return prev.filter((item: any) => item.id !== selectId);
|
||||
});
|
||||
setOpenDrawerBox(false);
|
||||
setSelectId(null);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
Toast.show({
|
||||
type: "error",
|
||||
text1: "Gagal menghapus data",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -37,25 +104,36 @@ export default function InvestmentRecapOfDocument() {
|
||||
/>
|
||||
|
||||
<ViewWrapper>
|
||||
{Array.from({ length: 10 }).map((_, index) => (
|
||||
<Investment_BoxDetailDocument
|
||||
key={index}
|
||||
title={`Judul Dokumen ${index + 1}`}
|
||||
leftIcon={
|
||||
<Ionicons
|
||||
name="ellipsis-horizontal-outline"
|
||||
size={ICON_SIZE_SMALL}
|
||||
color={MainColor.white}
|
||||
style={{
|
||||
zIndex: 10,
|
||||
alignSelf: "flex-end",
|
||||
}}
|
||||
onPress={() => setOpenDrawerBox(true)}
|
||||
/>
|
||||
}
|
||||
href={`/(file)/${id}`}
|
||||
/>
|
||||
))}
|
||||
{loadList ? (
|
||||
<LoaderCustom />
|
||||
) : _.isEmpty(list) ? (
|
||||
<TextCustom align="center" color="gray">
|
||||
Tidak ada data
|
||||
</TextCustom>
|
||||
) : (
|
||||
list?.map((item: any, index: number) => (
|
||||
<Investment_BoxDetailDocument
|
||||
key={index}
|
||||
title={item.title}
|
||||
leftIcon={
|
||||
<Ionicons
|
||||
name="ellipsis-horizontal-outline"
|
||||
size={ICON_SIZE_SMALL}
|
||||
color={MainColor.white}
|
||||
style={{
|
||||
zIndex: 10,
|
||||
alignSelf: "flex-end",
|
||||
}}
|
||||
onPress={() => {
|
||||
setSelectId(item.id);
|
||||
setOpenDrawerBox(true);
|
||||
}}
|
||||
/>
|
||||
}
|
||||
href={`/(file)/${item.fileId}`}
|
||||
/>
|
||||
))
|
||||
)}
|
||||
</ViewWrapper>
|
||||
|
||||
{/* Drawer On Header */}
|
||||
@@ -69,7 +147,7 @@ export default function InvestmentRecapOfDocument() {
|
||||
{
|
||||
icon: (
|
||||
<AntDesign
|
||||
name="pluscircle"
|
||||
name="plus-circle"
|
||||
size={ICON_SIZE_SMALL}
|
||||
color={MainColor.white}
|
||||
/>
|
||||
@@ -96,7 +174,7 @@ export default function InvestmentRecapOfDocument() {
|
||||
{
|
||||
icon: <IconEdit />,
|
||||
label: "Edit Dokumen",
|
||||
path: `/investment/${id}/(document)/edit-document`,
|
||||
path: `/investment/${selectId}/(document)/edit-document`,
|
||||
},
|
||||
{
|
||||
icon: (
|
||||
@@ -119,12 +197,14 @@ export default function InvestmentRecapOfDocument() {
|
||||
textLeft: "Batal",
|
||||
textRight: "Hapus",
|
||||
onPressRight: () => {
|
||||
setOpenDrawerBox(false);
|
||||
handlerDeleteDocument();
|
||||
},
|
||||
});
|
||||
} else {
|
||||
router.push(item.path as any);
|
||||
}
|
||||
router.push(item.path as any);
|
||||
setOpenDrawer(false);
|
||||
|
||||
setOpenDrawerBox(false);
|
||||
}}
|
||||
/>
|
||||
</DrawerCustom>
|
||||
|
||||
@@ -1,29 +1,56 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
BackButton,
|
||||
BaseBox,
|
||||
DotButton,
|
||||
DrawerCustom,
|
||||
Grid,
|
||||
MenuDrawerDynamicGrid,
|
||||
StackCustom,
|
||||
TextCustom,
|
||||
ViewWrapper,
|
||||
BackButton,
|
||||
BaseBox,
|
||||
DotButton,
|
||||
DrawerCustom,
|
||||
Grid,
|
||||
MenuDrawerDynamicGrid,
|
||||
StackCustom,
|
||||
TextCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import { IconDocument, IconEdit, IconNews } from "@/components/_Icon";
|
||||
import { IMenuDrawerItem } from "@/components/_Interface/types";
|
||||
import { MainColor } from "@/constants/color-palet";
|
||||
import { ICON_SIZE_MEDIUM } from "@/constants/constans-value";
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
import Invesment_ComponentBoxOnBottomDetail from "@/screens/Invesment/ComponentBoxOnBottomDetail";
|
||||
import Invesment_DetailDataPublishSection from "@/screens/Invesment/DetailDataPublishSection";
|
||||
import { apiInvestmentGetInvoice } from "@/service/api-client/api-investment";
|
||||
import { formatCurrencyDisplay } from "@/utils/formatCurrencyDisplay";
|
||||
import { AntDesign, MaterialIcons } from "@expo/vector-icons";
|
||||
import { router, Stack, useLocalSearchParams } from "expo-router";
|
||||
import { router, Stack, useFocusEffect, useLocalSearchParams } from "expo-router";
|
||||
import _ from "lodash";
|
||||
import { useState } from "react";
|
||||
import { useCallback, useState } from "react";
|
||||
|
||||
export default function InvestmentDetailHolding() {
|
||||
const { user } = useAuth();
|
||||
const { id, status } = useLocalSearchParams();
|
||||
const [openDrawerDraft, setOpenDrawerDraft] = useState(false);
|
||||
const [openDrawerPublish, setOpenDrawerPublish] = useState(false);
|
||||
const [data, setData] = useState<any>(null);
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
onLoadData();
|
||||
}, [id, status])
|
||||
);
|
||||
|
||||
const onLoadData = async () => {
|
||||
try {
|
||||
const response = await apiInvestmentGetInvoice({
|
||||
id: id as string,
|
||||
authorId: user?.id,
|
||||
category: "invoice",
|
||||
});
|
||||
|
||||
console.log("[DATA]", JSON.stringify(response.data, null, 2));
|
||||
setData(response.data);
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
}
|
||||
};
|
||||
|
||||
const handlePressDraft = (item: IMenuDrawerItem) => {
|
||||
console.log("PATH >> ", item.path);
|
||||
@@ -39,7 +66,8 @@ export default function InvestmentDetailHolding() {
|
||||
|
||||
const bottomSection = (
|
||||
<Invesment_ComponentBoxOnBottomDetail
|
||||
id={id as string}
|
||||
prospectusId={id as string}
|
||||
id={data?.Investasi?.id as string}
|
||||
status={"publish"}
|
||||
/>
|
||||
);
|
||||
@@ -64,10 +92,12 @@ export default function InvestmentDetailHolding() {
|
||||
<StackCustom gap={"xs"}>
|
||||
<Grid>
|
||||
<Grid.Col span={6}>
|
||||
<TextCustom bold>Nila Transaksi</TextCustom>
|
||||
<TextCustom bold>Nilai Transaksi</TextCustom>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={6}>
|
||||
<TextCustom bold>Rp. 7.500.000</TextCustom>
|
||||
<TextCustom bold>
|
||||
Rp. {data ? formatCurrencyDisplay(data?.nominal) : ""}
|
||||
</TextCustom>
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
<Grid>
|
||||
@@ -75,12 +105,16 @@ export default function InvestmentDetailHolding() {
|
||||
<TextCustom bold>Saham Terbeli</TextCustom>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={6}>
|
||||
<TextCustom bold>300 Lembar</TextCustom>
|
||||
<TextCustom bold>
|
||||
{data ? data?.lembarTerbeli : ""} Lembar
|
||||
</TextCustom>
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
</StackCustom>
|
||||
</BaseBox>
|
||||
|
||||
<Invesment_DetailDataPublishSection
|
||||
data={data && data?.Investasi}
|
||||
status={"publish"}
|
||||
bottomSection={bottomSection}
|
||||
/>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
AlertDefaultSystem,
|
||||
BackButton,
|
||||
@@ -8,37 +9,71 @@ import {
|
||||
MenuDrawerDynamicGrid,
|
||||
StackCustom,
|
||||
TextCustom,
|
||||
ViewWrapper
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import { IconTrash } from "@/components/_Icon/IconTrash";
|
||||
import { router, Stack, useLocalSearchParams } from "expo-router";
|
||||
import { useState } from "react";
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
import {
|
||||
apiInvestmentDeleteNews,
|
||||
apiInvestmentGetNews,
|
||||
} from "@/service/api-client/api-investment";
|
||||
import {
|
||||
router,
|
||||
Stack,
|
||||
useFocusEffect,
|
||||
useLocalSearchParams,
|
||||
} from "expo-router";
|
||||
import { useCallback, useState } from "react";
|
||||
import Toast from "react-native-toast-message";
|
||||
|
||||
export default function InvestmentNews() {
|
||||
const { id, news } = useLocalSearchParams();
|
||||
const { user } = useAuth();
|
||||
const { news } = useLocalSearchParams();
|
||||
const id = news as string;
|
||||
const [openDrawer, setOpenDrawer] = useState(false);
|
||||
const [data, setData] = useState<any | null>(null);
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
onLoadData();
|
||||
}, [id])
|
||||
);
|
||||
|
||||
const onLoadData = async () => {
|
||||
try {
|
||||
const response = await apiInvestmentGetNews({
|
||||
id: id,
|
||||
category: "one-news",
|
||||
});
|
||||
|
||||
setData(response.data);
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Stack.Screen
|
||||
options={{
|
||||
title: "Detail Berita",
|
||||
headerLeft: () => <BackButton />,
|
||||
headerRight: () => <DotButton onPress={() => setOpenDrawer(true)} />,
|
||||
headerRight: () =>
|
||||
user?.id === data?.authorId && (
|
||||
<DotButton onPress={() => setOpenDrawer(true)} />
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<ViewWrapper>
|
||||
<BaseBox>
|
||||
<StackCustom>
|
||||
<DummyLandscapeImage />
|
||||
{data && data?.imageId && (
|
||||
<DummyLandscapeImage imageId={data?.imageId || ""} />
|
||||
)}
|
||||
<TextCustom bold align="center" size="large">
|
||||
Judul Berita {news} Terbaru
|
||||
</TextCustom>
|
||||
<TextCustom>
|
||||
Lorem ipsum dolor sit amet consectetur adipisicing elit. Laborum
|
||||
fuga mollitia laboriosam voluptatibus quos molestias, illo fugiat
|
||||
esse repellat, ad officia earum numquam? Aliquid corrupti quam
|
||||
tempora cum harum est!
|
||||
{(data && data?.title) || "-"}
|
||||
</TextCustom>
|
||||
<TextCustom>{(data && data?.deskripsi) || "-"}</TextCustom>
|
||||
</StackCustom>
|
||||
</BaseBox>
|
||||
</ViewWrapper>
|
||||
@@ -52,7 +87,7 @@ export default function InvestmentNews() {
|
||||
data={[
|
||||
{
|
||||
label: "Hapus Berita",
|
||||
path: `/investment/${id}/add-news`,
|
||||
path: ``,
|
||||
icon: <IconTrash />,
|
||||
color: "red",
|
||||
},
|
||||
@@ -63,9 +98,26 @@ export default function InvestmentNews() {
|
||||
message: "Apakah Anda yakin ingin menghapus berita ini?",
|
||||
textLeft: "Batal",
|
||||
textRight: "Hapus",
|
||||
onPressRight: () => {
|
||||
router.back();
|
||||
setOpenDrawer(false);
|
||||
onPressRight: async () => {
|
||||
try {
|
||||
const response = await apiInvestmentDeleteNews({ id });
|
||||
|
||||
if (response.success) {
|
||||
Toast.show({
|
||||
type: "success",
|
||||
text1: "Berita berhasil dihapus",
|
||||
});
|
||||
router.back();
|
||||
setOpenDrawer(false);
|
||||
} else {
|
||||
Toast.show({
|
||||
type: "error",
|
||||
text1: "Gagal menghapus berita",
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
}
|
||||
},
|
||||
});
|
||||
}}
|
||||
|
||||
@@ -9,17 +9,89 @@ import {
|
||||
TextInputCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import { router } from "expo-router";
|
||||
import DIRECTORY_ID from "@/constants/directory-id";
|
||||
import { apiInvestmentCreateNews } from "@/service/api-client/api-investment";
|
||||
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 InvestmentAddNews() {
|
||||
const { id } = useLocalSearchParams();
|
||||
const [image, setImage] = useState<IFileData | null>(null);
|
||||
const [data, setData] = useState({
|
||||
title: "",
|
||||
deskripsi: "",
|
||||
});
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const handlerSubmit = async () => {
|
||||
let imageId = "";
|
||||
|
||||
if (!data.title || !data.deskripsi) {
|
||||
Toast.show({
|
||||
type: "error",
|
||||
text1: "Judul dan deskripsi harus diisi",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
setIsLoading(true);
|
||||
if (image) {
|
||||
const uploadImage = await uploadFileService({
|
||||
dirId: DIRECTORY_ID.investasi_berita,
|
||||
imageUri: image.uri,
|
||||
});
|
||||
|
||||
imageId = uploadImage.data.id;
|
||||
}
|
||||
|
||||
const newData = {
|
||||
id: id as string,
|
||||
title: data.title,
|
||||
deskripsi: data.deskripsi,
|
||||
imageId: imageId,
|
||||
};
|
||||
|
||||
const response = await apiInvestmentCreateNews({
|
||||
id: id as string,
|
||||
data: newData,
|
||||
});
|
||||
|
||||
if (response.success) {
|
||||
Toast.show({
|
||||
type: "success",
|
||||
text1: "Berita berhasil disimpan",
|
||||
});
|
||||
router.back();
|
||||
} else {
|
||||
Toast.show({
|
||||
type: "error",
|
||||
text1: "Gagal menyimpan berita",
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<ViewWrapper>
|
||||
<StackCustom gap={"xs"}>
|
||||
<InformationBox text="Pengunggahan foto ke aplikasi bersifat opsional dan tidak diwajibkan, Anda dapat menyimpan berita tanpa mengunggah foto." />
|
||||
<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 +102,8 @@ export default function InvestmentAddNews() {
|
||||
label="Judul Berita"
|
||||
placeholder="Masukan judul berita"
|
||||
required
|
||||
value={data.title}
|
||||
onChangeText={(value) => setData({ ...data, title: value })}
|
||||
/>
|
||||
<TextAreaCustom
|
||||
label="Deskripsi Berita"
|
||||
@@ -37,13 +111,11 @@ export default function InvestmentAddNews() {
|
||||
required
|
||||
showCount
|
||||
maxLength={1000}
|
||||
value={data.deskripsi}
|
||||
onChangeText={(value) => setData({ ...data, deskripsi: value })}
|
||||
/>
|
||||
|
||||
<ButtonCustom
|
||||
onPress={() => {
|
||||
router.back();
|
||||
}}
|
||||
>
|
||||
<ButtonCustom isLoading={isLoading} onPress={handlerSubmit}>
|
||||
Simpan
|
||||
</ButtonCustom>
|
||||
</StackCustom>
|
||||
|
||||
@@ -1,18 +1,51 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
BackButton,
|
||||
BaseBox,
|
||||
DrawerCustom,
|
||||
MenuDrawerDynamicGrid,
|
||||
TextCustom,
|
||||
ViewWrapper
|
||||
BackButton,
|
||||
BaseBox,
|
||||
DrawerCustom,
|
||||
LoaderCustom,
|
||||
MenuDrawerDynamicGrid,
|
||||
TextCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import { IconPlus } from "@/components/_Icon";
|
||||
import { router, Stack, useLocalSearchParams } from "expo-router";
|
||||
import { useState } from "react";
|
||||
import { apiInvestmentGetNews } from "@/service/api-client/api-investment";
|
||||
import {
|
||||
router,
|
||||
Stack,
|
||||
useFocusEffect,
|
||||
useLocalSearchParams,
|
||||
} from "expo-router";
|
||||
import _ from "lodash";
|
||||
import { useCallback, useState } from "react";
|
||||
|
||||
export default function InvestmentListOfNews() {
|
||||
const { id } = useLocalSearchParams();
|
||||
const [openDrawer, setOpenDrawer] = useState(false);
|
||||
const [list, setList] = useState<any[] | null>(null);
|
||||
const [loadList, setLoadList] = useState(false);
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
onLoadList();
|
||||
}, [id])
|
||||
);
|
||||
|
||||
const onLoadList = async () => {
|
||||
try {
|
||||
setLoadList(true);
|
||||
const response = await apiInvestmentGetNews({
|
||||
id: id as string,
|
||||
category: "all-news",
|
||||
});
|
||||
|
||||
setList(response.data);
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
} finally {
|
||||
setLoadList(false);
|
||||
}
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<Stack.Screen
|
||||
@@ -22,16 +55,25 @@ export default function InvestmentListOfNews() {
|
||||
// headerRight: () => <DotButton onPress={() => setOpenDrawer(true)} />,
|
||||
}}
|
||||
/>
|
||||
|
||||
<ViewWrapper>
|
||||
{Array.from({ length: 15 }).map((_, index) => (
|
||||
<BaseBox
|
||||
key={index}
|
||||
paddingBlock={5}
|
||||
href={`/investment/${id}/(news)/${index + 1}`}
|
||||
>
|
||||
<TextCustom bold>Berita Terbaru {index + 1}</TextCustom>
|
||||
</BaseBox>
|
||||
))}
|
||||
{loadList ? (
|
||||
<LoaderCustom />
|
||||
) : _.isEmpty(list) ? (
|
||||
<TextCustom align="center" color="gray">
|
||||
Tidak ada data
|
||||
</TextCustom>
|
||||
) : (
|
||||
list?.map((item: any, index: number) => (
|
||||
<BaseBox
|
||||
key={index}
|
||||
paddingBlock={5}
|
||||
href={`/investment/[id]/(news)/${item.id}`}
|
||||
>
|
||||
<TextCustom bold>{item.title}</TextCustom>
|
||||
</BaseBox>
|
||||
))
|
||||
)}
|
||||
</ViewWrapper>
|
||||
|
||||
<DrawerCustom
|
||||
|
||||
@@ -1,19 +1,53 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
BackButton,
|
||||
BaseBox,
|
||||
DotButton,
|
||||
DrawerCustom,
|
||||
LoaderCustom,
|
||||
MenuDrawerDynamicGrid,
|
||||
TextCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import { IconPlus } from "@/components/_Icon";
|
||||
import { router, Stack, useLocalSearchParams } from "expo-router";
|
||||
import { useState } from "react";
|
||||
import { apiInvestmentGetNews } from "@/service/api-client/api-investment";
|
||||
import {
|
||||
router,
|
||||
Stack,
|
||||
useFocusEffect,
|
||||
useLocalSearchParams,
|
||||
} from "expo-router";
|
||||
import _ from "lodash";
|
||||
import { useCallback, useState } from "react";
|
||||
|
||||
export default function InvestmentRecapOfNews() {
|
||||
const { id } = useLocalSearchParams();
|
||||
const [openDrawer, setOpenDrawer] = useState(false);
|
||||
const [list, setList] = useState<any[] | null>(null);
|
||||
const [loadList, setLoadList] = useState(false);
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
onLoadList();
|
||||
}, [id])
|
||||
);
|
||||
|
||||
const onLoadList = async () => {
|
||||
try {
|
||||
setLoadList(true);
|
||||
const response = await apiInvestmentGetNews({
|
||||
id: id as string,
|
||||
category: "all-news",
|
||||
});
|
||||
|
||||
setList(response.data);
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
} finally {
|
||||
setLoadList(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Stack.Screen
|
||||
@@ -24,15 +58,23 @@ export default function InvestmentRecapOfNews() {
|
||||
}}
|
||||
/>
|
||||
<ViewWrapper>
|
||||
{Array.from({ length: 15 }).map((_, index) => (
|
||||
<BaseBox
|
||||
key={index}
|
||||
paddingBlock={5}
|
||||
href={`/investment/${id}/(news)/${index + 1}`}
|
||||
>
|
||||
<TextCustom bold>Berita Terbaru {index + 1}</TextCustom>
|
||||
</BaseBox>
|
||||
))}
|
||||
{loadList ? (
|
||||
<LoaderCustom />
|
||||
) : _.isEmpty(list) ? (
|
||||
<TextCustom align="center" color="gray">
|
||||
Tidak ada data
|
||||
</TextCustom>
|
||||
) : (
|
||||
list?.map((item: any, index: number) => (
|
||||
<BaseBox
|
||||
key={index}
|
||||
paddingBlock={5}
|
||||
href={`/investment/[id]/(news)/${item.id}`}
|
||||
>
|
||||
<TextCustom bold>{item.title}</TextCustom>
|
||||
</BaseBox>
|
||||
))
|
||||
)}
|
||||
</ViewWrapper>
|
||||
|
||||
<DrawerCustom
|
||||
|
||||
@@ -1,9 +1,73 @@
|
||||
import { BaseBox, Grid, Spacing, StackCustom, TextCustom, ViewWrapper } from "@/components";
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
BaseBox,
|
||||
Grid,
|
||||
Spacing,
|
||||
StackCustom,
|
||||
TextCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import { MainColor } from "@/constants/color-palet";
|
||||
import { apiInvestmentGetInvoice } from "@/service/api-client/api-investment";
|
||||
import { GStyles } from "@/styles/global-styles";
|
||||
import { dateTimeView } from "@/utils/dateTimeView";
|
||||
import { formatCurrencyDisplay } from "@/utils/formatCurrencyDisplay";
|
||||
import { FontAwesome6 } from "@expo/vector-icons";
|
||||
import { useLocalSearchParams, useFocusEffect } from "expo-router";
|
||||
import React from "react";
|
||||
|
||||
export default function InvestmentFailed() {
|
||||
const { id } = useLocalSearchParams();
|
||||
console.log("[ID]", id);
|
||||
|
||||
const [data, setData] = React.useState<any | null>(null);
|
||||
|
||||
useFocusEffect(
|
||||
React.useCallback(() => {
|
||||
onLoadData();
|
||||
}, [id])
|
||||
);
|
||||
const onLoadData = async () => {
|
||||
try {
|
||||
const response = await apiInvestmentGetInvoice({
|
||||
id: id as string,
|
||||
category: "invoice",
|
||||
});
|
||||
|
||||
console.log("[RES INVOICE]", 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",
|
||||
value: `Rp ${data && formatCurrencyDisplay(data?.nominal)}` || "-",
|
||||
},
|
||||
{
|
||||
label: "Tanggal",
|
||||
value: (data && dateTimeView({ date: data?.createdAt })) || "-",
|
||||
},
|
||||
{
|
||||
label: "Lembar Terbeli",
|
||||
value: (data && formatCurrencyDisplay(data?.lembarTerbeli)) || "-",
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<ViewWrapper>
|
||||
<StackCustom>
|
||||
@@ -11,8 +75,7 @@ export default function InvestmentFailed() {
|
||||
<StackCustom>
|
||||
<TextCustom bold align="center">
|
||||
Transaksi anda gagal karena bukti transfer tidak sesuai dengan
|
||||
data kami. Jika ini masalah khusus silahkan hubungi pada kontak
|
||||
whatsapp kami.
|
||||
data kami. Hubungi admin untuk memperbaiki masalah ini.
|
||||
</TextCustom>
|
||||
|
||||
<FontAwesome6
|
||||
@@ -50,30 +113,3 @@ export default function InvestmentFailed() {
|
||||
</ViewWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
const listData = [
|
||||
{
|
||||
label: "Bank",
|
||||
value: " BCA",
|
||||
},
|
||||
{
|
||||
label: "Rekening Penerima",
|
||||
value: "Himpunan Pengusaha Muda Indonesia",
|
||||
},
|
||||
{
|
||||
label: "No Rekening",
|
||||
value: "2304235678854332",
|
||||
},
|
||||
{
|
||||
label: "Jumlah",
|
||||
value: "Rp. 1.000.000",
|
||||
},
|
||||
{
|
||||
label: "Tanggal",
|
||||
value: "2022-01-01",
|
||||
},
|
||||
{
|
||||
label: "Lembar Terbeli",
|
||||
value: "100",
|
||||
},
|
||||
];
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
BaseBox,
|
||||
BoxButtonOnFooter,
|
||||
@@ -9,16 +10,89 @@ import {
|
||||
TextInputCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import { router, useLocalSearchParams } from "expo-router";
|
||||
import { LOCAL_STORAGE_KEY } from "@/constants/local-storage-key";
|
||||
import { apiInvestmentGetOne } from "@/service/api-client/api-investment";
|
||||
import { formatCurrencyDisplay } from "@/utils/formatCurrencyDisplay";
|
||||
import AsyncStorage from "@react-native-async-storage/async-storage";
|
||||
import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
|
||||
import { useCallback, useState } from "react";
|
||||
|
||||
export default function InvestmentInvest() {
|
||||
const { id } = useLocalSearchParams();
|
||||
// console.log("[ID]", id);
|
||||
const [data, setData] = useState<any>(null);
|
||||
const [jumlah, setJumlah] = useState<number>(0);
|
||||
const [total, setTotal] = useState<number>(0);
|
||||
const [sisaLembar, setSisaLembar] = useState<number>(0);
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
onLoadData();
|
||||
}, [id])
|
||||
);
|
||||
|
||||
const onLoadData = async () => {
|
||||
try {
|
||||
const response = await apiInvestmentGetOne({
|
||||
id: id as string,
|
||||
});
|
||||
|
||||
setData(response.data);
|
||||
setSisaLembar(response.data?.sisaLembar);
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleTextChange = (text: string) => {
|
||||
// Izinkan input kosong → anggap sebagai 0 (atau abaikan, tergantung UX)
|
||||
if (text === "") {
|
||||
setJumlah(0);
|
||||
setTotal(0);
|
||||
return;
|
||||
}
|
||||
|
||||
// Regex: hanya digit (angka bulat positif)
|
||||
const integerRegex = /^\d+$/;
|
||||
|
||||
if (integerRegex.test(text)) {
|
||||
const numValue = Number(text);
|
||||
// Karena regex sudah pastikan hanya angka, isNaN biasanya false
|
||||
// Tapi tetap aman untuk cek
|
||||
if (!isNaN(numValue)) {
|
||||
setJumlah(numValue);
|
||||
setTotal(numValue * Number(data?.hargaLembar));
|
||||
console.log("[VALUE]", numValue);
|
||||
}
|
||||
}
|
||||
// Jika input tidak valid (misal: "12a", "12."), abaikan → state tidak berubah
|
||||
};
|
||||
|
||||
const buttonSubmit = () => {
|
||||
return (
|
||||
<>
|
||||
<BoxButtonOnFooter>
|
||||
<ButtonCustom onPress={() => router.push(`/investment/${id}/select-bank`)}>Beli</ButtonCustom>
|
||||
<ButtonCustom
|
||||
isLoading={isLoading}
|
||||
disabled={jumlah < 10 || jumlah > sisaLembar}
|
||||
onPress={async () => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
await AsyncStorage.setItem(
|
||||
LOCAL_STORAGE_KEY.transactionInvestment,
|
||||
JSON.stringify({ jumlah, total })
|
||||
);
|
||||
router.push(`/investment/${id}/select-bank`);
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
}}
|
||||
>
|
||||
Beli
|
||||
</ButtonCustom>
|
||||
</BoxButtonOnFooter>
|
||||
</>
|
||||
);
|
||||
@@ -34,7 +108,9 @@ export default function InvestmentInvest() {
|
||||
<TextCustom>Sisa Lembar Saham</TextCustom>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={6} style={{ alignItems: "flex-end" }}>
|
||||
<TextCustom>3.000</TextCustom>
|
||||
<TextCustom>
|
||||
{data && formatCurrencyDisplay(data?.sisaLembar) || "-"}
|
||||
</TextCustom>
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
<Grid>
|
||||
@@ -42,7 +118,9 @@ export default function InvestmentInvest() {
|
||||
<TextCustom>Harga Per Lembar</TextCustom>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={6} style={{ alignItems: "flex-end" }}>
|
||||
<TextCustom>Rp. 1.000</TextCustom>
|
||||
<TextCustom>
|
||||
{data && formatCurrencyDisplay(data?.hargaLembar) || "-"}
|
||||
</TextCustom>
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
<Grid>
|
||||
@@ -69,6 +147,10 @@ export default function InvestmentInvest() {
|
||||
}}
|
||||
placeholder="0"
|
||||
keyboardType="numeric"
|
||||
value={jumlah.toString()}
|
||||
onChangeText={(value) => {
|
||||
handleTextChange(value);
|
||||
}}
|
||||
/>
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
@@ -78,7 +160,7 @@ export default function InvestmentInvest() {
|
||||
<TextCustom>Total Harga</TextCustom>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={6} style={{ alignItems: "flex-end" }}>
|
||||
<TextCustom>Rp. 1.000</TextCustom>
|
||||
<TextCustom> Rp. {formatCurrencyDisplay(total)}</TextCustom>
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
</StackCustom>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
BaseBox,
|
||||
ButtonCenteredOnly,
|
||||
@@ -9,11 +10,96 @@ import {
|
||||
TextCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import CopyButton from "@/components/Button/CoyButton";
|
||||
import { MainColor } from "@/constants/color-palet";
|
||||
import { router, useLocalSearchParams } from "expo-router";
|
||||
import DIRECTORY_ID from "@/constants/directory-id";
|
||||
import {
|
||||
apiInvestmentGetInvoice,
|
||||
apiInvestmentUpdateInvoice,
|
||||
} from "@/service/api-client/api-investment";
|
||||
import { uploadFileService } from "@/service/upload-service";
|
||||
import { formatCurrencyDisplay } from "@/utils/formatCurrencyDisplay";
|
||||
import pickFile, { IFileData } from "@/utils/pickFile";
|
||||
import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
|
||||
import { useCallback, useState } from "react";
|
||||
import { View } from "react-native";
|
||||
import Toast from "react-native-toast-message";
|
||||
|
||||
export default function InvestmentInvoice() {
|
||||
const { id } = useLocalSearchParams();
|
||||
console.log("[ID]", id);
|
||||
const [data, setData] = useState<any>({});
|
||||
const [image, setImage] = useState<IFileData>({
|
||||
name: "",
|
||||
uri: "",
|
||||
size: 0,
|
||||
});
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
onLoadData();
|
||||
}, [id])
|
||||
);
|
||||
|
||||
const onLoadData = async () => {
|
||||
try {
|
||||
const response = await apiInvestmentGetInvoice({
|
||||
id: id as string,
|
||||
category: "invoice",
|
||||
});
|
||||
|
||||
console.log("[RES INVOICE]", JSON.stringify(response.data, null, 2));
|
||||
setData(response.data);
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
}
|
||||
};
|
||||
|
||||
const handlerSubmitUpdate = async () => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
const responseUploadImage = await uploadFileService({
|
||||
dirId: DIRECTORY_ID.investasi_bukti_transfer,
|
||||
imageUri: image?.uri,
|
||||
});
|
||||
|
||||
console.log("[RESPONSE UPLOAD IMAGE]", responseUploadImage);
|
||||
|
||||
if (!responseUploadImage?.data?.id) {
|
||||
Toast.show({
|
||||
type: "error",
|
||||
text1: "Gagal mengunggah bukti transfer",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const response = await apiInvestmentUpdateInvoice({
|
||||
id: id as string,
|
||||
data: {
|
||||
imageId: responseUploadImage?.data?.id,
|
||||
},
|
||||
status: "proses",
|
||||
});
|
||||
|
||||
if (response.success) {
|
||||
console.log(
|
||||
"[RESPONSE UPDATE]",
|
||||
JSON.stringify(response.data, null, 2)
|
||||
);
|
||||
Toast.show({
|
||||
type: "success",
|
||||
text1: "Berhasil mengunggah bukti transfer",
|
||||
});
|
||||
router.push(`/investment/${id}/(transaction-flow)/process`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<ViewWrapper>
|
||||
@@ -21,9 +107,23 @@ export default function InvestmentInvoice() {
|
||||
<InformationBox text="Mohon transfer ke rekening dibawah" />
|
||||
<BaseBox>
|
||||
<StackCustom gap={"xs"}>
|
||||
<TextCustom>Nama BANK</TextCustom>
|
||||
<TextCustom>Nama Penerima</TextCustom>
|
||||
<Grid>
|
||||
<Grid.Col span={4}>
|
||||
<TextCustom>Bank</TextCustom>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={8}>
|
||||
<TextCustom>{data?.MasterBank?.namaBank}</TextCustom>
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
<Spacing height={10} />
|
||||
<Grid>
|
||||
<Grid.Col span={4}>
|
||||
<TextCustom>Nama Akun</TextCustom>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={8}>
|
||||
<TextCustom>{data?.MasterBank?.namaAkun}</TextCustom>
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
|
||||
<BaseBox backgroundColor={MainColor.soft_darkblue}>
|
||||
<Grid containerStyle={{ justifyContent: "center" }}>
|
||||
@@ -34,7 +134,7 @@ export default function InvestmentInvoice() {
|
||||
}}
|
||||
>
|
||||
<TextCustom size="xlarge" bold color="yellow">
|
||||
4567898765433567
|
||||
{data?.MasterBank?.norek}
|
||||
</TextCustom>
|
||||
</Grid.Col>
|
||||
<Grid.Col
|
||||
@@ -43,7 +143,7 @@ export default function InvestmentInvoice() {
|
||||
alignItems: "flex-end",
|
||||
}}
|
||||
>
|
||||
<ButtonCustom>Salin</ButtonCustom>
|
||||
<CopyButton textToCopy={data?.MasterBank?.norek} />
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
</BaseBox>
|
||||
@@ -65,7 +165,7 @@ export default function InvestmentInvoice() {
|
||||
}}
|
||||
>
|
||||
<TextCustom size="xlarge" bold color="yellow">
|
||||
Rp. 1.000.000
|
||||
Rp. {formatCurrencyDisplay(data?.nominal)}
|
||||
</TextCustom>
|
||||
</Grid.Col>
|
||||
<Grid.Col
|
||||
@@ -74,7 +174,7 @@ export default function InvestmentInvoice() {
|
||||
alignItems: "flex-end",
|
||||
}}
|
||||
>
|
||||
<ButtonCustom>Salin</ButtonCustom>
|
||||
<CopyButton textToCopy={data?.nominal} />
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
</BaseBox>
|
||||
@@ -83,10 +183,37 @@ export default function InvestmentInvoice() {
|
||||
|
||||
<BaseBox>
|
||||
<StackCustom>
|
||||
<TextCustom>Upload bukti transfer anda.</TextCustom>
|
||||
<TextCustom align="center">
|
||||
Upload bukti transfer anda.
|
||||
</TextCustom>
|
||||
{image ? (
|
||||
<View
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
gap: 10,
|
||||
paddingInline: 20,
|
||||
}}
|
||||
>
|
||||
<TextCustom bold align="center" truncate>
|
||||
{image?.name}
|
||||
</TextCustom>
|
||||
</View>
|
||||
) : (
|
||||
<TextCustom align="center">
|
||||
Tidak ada gambar yang diunggah
|
||||
</TextCustom>
|
||||
)}
|
||||
<ButtonCenteredOnly
|
||||
onPress={() => {
|
||||
router.push("/(application)/(image)/take-picture/123");
|
||||
pickFile({
|
||||
allowedType: "image",
|
||||
setImageUri(file: any) {
|
||||
console.log("[IMAGE]", file);
|
||||
setImage(file);
|
||||
},
|
||||
});
|
||||
}}
|
||||
icon="upload"
|
||||
>
|
||||
@@ -96,14 +223,16 @@ export default function InvestmentInvoice() {
|
||||
</BaseBox>
|
||||
|
||||
<ButtonCustom
|
||||
isLoading={isLoading}
|
||||
disabled={!image}
|
||||
onPress={() => {
|
||||
router.push(`/investment/${id}/(transaction-flow)/process`);
|
||||
handlerSubmitUpdate();
|
||||
}}
|
||||
>
|
||||
Saya Sudah Transfer
|
||||
</ButtonCustom>
|
||||
</StackCustom>
|
||||
<Spacing/>
|
||||
<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 InvestmentProcess() {
|
||||
return (
|
||||
@@ -16,21 +9,24 @@ export default function InvestmentProcess() {
|
||||
<BaseBox>
|
||||
<StackCustom>
|
||||
<TextCustom align="center" bold>
|
||||
Admin sedang memproses transaksi investasimu
|
||||
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'}}>
|
||||
<Grid.Col span={10} style={{ justifyContent: "center" }}>
|
||||
<TextCustom size="small">
|
||||
Hubungi admin jika tidak kunjung di proses! Klik pada logo
|
||||
Whatsapp ini.
|
||||
</TextCustom>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={2} style={{alignItems: "flex-end"}}>
|
||||
<Grid.Col span={2} style={{ alignItems: "flex-end" }}>
|
||||
<Ionicons
|
||||
name="logo-whatsapp"
|
||||
size={50}
|
||||
@@ -38,7 +34,7 @@ export default function InvestmentProcess() {
|
||||
/>
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
</BaseBox>
|
||||
</BaseBox> */}
|
||||
</ViewWrapper>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -5,35 +5,97 @@ 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 { apiInvestmentCreateInvoice } from "@/service/api-client/api-investment";
|
||||
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 InvestmentSelectBank() {
|
||||
const { user } = useAuth();
|
||||
const { id } = useLocalSearchParams();
|
||||
const [value, setValue] = useState<any | number>("");
|
||||
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 dataCheckout = await AsyncStorage.getItem(
|
||||
LOCAL_STORAGE_KEY.transactionInvestment
|
||||
);
|
||||
if (dataCheckout) {
|
||||
const storage = JSON.parse(dataCheckout);
|
||||
const newData = {
|
||||
...storage,
|
||||
bankId: select,
|
||||
authorId: user?.id,
|
||||
};
|
||||
|
||||
const response = await apiInvestmentCreateInvoice({
|
||||
id: id as string,
|
||||
data: newData,
|
||||
});
|
||||
|
||||
if (response.success) {
|
||||
console.log("[RESPONSE >>]", response);
|
||||
const invoiceId = response.data.id;
|
||||
|
||||
const delStorage = await AsyncStorage.removeItem(
|
||||
LOCAL_STORAGE_KEY.transactionInvestment
|
||||
);
|
||||
|
||||
console.log("[DEL STORAGE]", delStorage);
|
||||
router.replace(`/investment/${invoiceId}/invoice`);
|
||||
} else {
|
||||
console.log("[FAILED]", response);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const buttonSubmit = () => {
|
||||
return (
|
||||
<>
|
||||
<BoxButtonOnFooter>
|
||||
<ButtonCustom
|
||||
onPress={() => router.replace(`/investment/${id}/invoice`)}
|
||||
>
|
||||
Pilih
|
||||
</ButtonCustom>
|
||||
<ButtonCustom isLoading={isLoading} onPress={() => handlerSubmit()}>Pilih</ButtonCustom>
|
||||
</BoxButtonOnFooter>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
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) => (
|
||||
<BaseBox key={item.id}>
|
||||
<RadioCustom label={item.namaBank} value={item.id} />
|
||||
</BaseBox>
|
||||
))}
|
||||
</RadioGroup>
|
||||
</ViewWrapper>
|
||||
);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
BaseBox,
|
||||
Grid,
|
||||
@@ -7,10 +8,66 @@ import {
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import { MainColor } from "@/constants/color-palet";
|
||||
import { apiInvestmentGetInvoice } from "@/service/api-client/api-investment";
|
||||
import { GStyles } from "@/styles/global-styles";
|
||||
import { dateTimeView } from "@/utils/dateTimeView";
|
||||
import { formatCurrencyDisplay } from "@/utils/formatCurrencyDisplay";
|
||||
import { FontAwesome6 } from "@expo/vector-icons";
|
||||
import { useFocusEffect, useLocalSearchParams } from "expo-router";
|
||||
import React from "react";
|
||||
|
||||
export default function InvestmentSuccess() {
|
||||
const { id } = useLocalSearchParams();
|
||||
console.log("[ID]", id);
|
||||
|
||||
const [data, setData] = React.useState<any | null>(null);
|
||||
|
||||
useFocusEffect(
|
||||
React.useCallback(() => {
|
||||
onLoadData();
|
||||
}, [id])
|
||||
);
|
||||
const onLoadData = async () => {
|
||||
try {
|
||||
const response = await apiInvestmentGetInvoice({
|
||||
id: id as string,
|
||||
category: "invoice",
|
||||
});
|
||||
|
||||
console.log("[RES INVOICE]", 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",
|
||||
value: `Rp ${data && formatCurrencyDisplay(data?.nominal)}` || "-",
|
||||
},
|
||||
{
|
||||
label: "Tanggal",
|
||||
value: (data && dateTimeView({ date: data?.createdAt })) || "-",
|
||||
},
|
||||
{
|
||||
label: "Lembar Terbeli",
|
||||
value: (data && formatCurrencyDisplay(data?.lembarTerbeli)) || "-",
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<ViewWrapper>
|
||||
<StackCustom>
|
||||
@@ -35,8 +92,7 @@ export default function InvestmentSuccess() {
|
||||
Detail Transaksi
|
||||
</TextCustom>
|
||||
|
||||
<Spacing/>
|
||||
|
||||
<Spacing />
|
||||
|
||||
<StackCustom>
|
||||
{listData.map((item, i) => (
|
||||
@@ -45,7 +101,9 @@ export default function InvestmentSuccess() {
|
||||
<TextCustom bold>{item.label}</TextCustom>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={7}>
|
||||
<TextCustom style={{paddingLeft: 10}}>{item.value}</TextCustom>
|
||||
<TextCustom style={{ paddingLeft: 10 }}>
|
||||
{item.value}
|
||||
</TextCustom>
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
))}
|
||||
@@ -55,30 +113,3 @@ export default function InvestmentSuccess() {
|
||||
</ViewWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
const listData = [
|
||||
{
|
||||
label: "Bank",
|
||||
value: " BCA",
|
||||
},
|
||||
{
|
||||
label: "Rekening Penerima",
|
||||
value: "Himpunan Pengusaha Muda Indonesia",
|
||||
},
|
||||
{
|
||||
label: "No Rekening",
|
||||
value: "2304235678854332",
|
||||
},
|
||||
{
|
||||
label: "Jumlah",
|
||||
value: "Rp. 1.000.000",
|
||||
},
|
||||
{
|
||||
label: "Tanggal",
|
||||
value: "2022-01-01",
|
||||
},
|
||||
{
|
||||
label: "Lembar Terbeli",
|
||||
value: "100",
|
||||
},
|
||||
];
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
BackButton,
|
||||
DotButton,
|
||||
@@ -9,19 +10,47 @@ import { IconDocument, IconEdit, IconNews } from "@/components/_Icon";
|
||||
import { IMenuDrawerItem } from "@/components/_Interface/types";
|
||||
import { MainColor } from "@/constants/color-palet";
|
||||
import { ICON_SIZE_MEDIUM } from "@/constants/constans-value";
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
import Investment_ButtonInvestasiSection from "@/screens/Invesment/ButtonInvestasiSection";
|
||||
import Invesment_ComponentBoxOnBottomDetail from "@/screens/Invesment/ComponentBoxOnBottomDetail";
|
||||
import Invesment_DetailDataPublishSection from "@/screens/Invesment/DetailDataPublishSection";
|
||||
import { apiInvestmentGetOne } from "@/service/api-client/api-investment";
|
||||
import { AntDesign, MaterialIcons } from "@expo/vector-icons";
|
||||
import { router, Stack, useLocalSearchParams } from "expo-router";
|
||||
import {
|
||||
router,
|
||||
Stack,
|
||||
useFocusEffect,
|
||||
useLocalSearchParams,
|
||||
} from "expo-router";
|
||||
import _ from "lodash";
|
||||
import { useState } from "react";
|
||||
import { useCallback, useState } from "react";
|
||||
|
||||
export default function InvestmentDetailStatus() {
|
||||
const { user } = useAuth();
|
||||
const { id, status } = useLocalSearchParams();
|
||||
const [openDrawerDraft, setOpenDrawerDraft] = useState(false);
|
||||
const [openDrawerPublish, setOpenDrawerPublish] = useState(false);
|
||||
|
||||
const [data, setData] = useState<any>(null);
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
onLoadData();
|
||||
}, [id, status])
|
||||
);
|
||||
|
||||
const onLoadData = async () => {
|
||||
try {
|
||||
const response = await apiInvestmentGetOne({
|
||||
id: id as string,
|
||||
});
|
||||
|
||||
setData(response.data);
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
}
|
||||
};
|
||||
|
||||
const handlePressDraft = (item: IMenuDrawerItem) => {
|
||||
console.log("PATH >> ", item.path);
|
||||
router.navigate(item.path as any);
|
||||
@@ -36,13 +65,14 @@ export default function InvestmentDetailStatus() {
|
||||
|
||||
const bottomSection = (
|
||||
<Invesment_ComponentBoxOnBottomDetail
|
||||
id={id as string}
|
||||
id={data?.id}
|
||||
prospectusId={data?.prospektusFileId}
|
||||
status={status as string}
|
||||
/>
|
||||
);
|
||||
|
||||
const buttonSection = (
|
||||
<Investment_ButtonInvestasiSection id={id as string} isMine={false} />
|
||||
<Investment_ButtonInvestasiSection id={id as string} isMine={user?.id === data?.author?.id} />
|
||||
);
|
||||
|
||||
return (
|
||||
@@ -63,6 +93,7 @@ export default function InvestmentDetailStatus() {
|
||||
<ViewWrapper>
|
||||
<Invesment_DetailDataPublishSection
|
||||
status={status as string}
|
||||
data={data}
|
||||
bottomSection={bottomSection}
|
||||
buttonSection={buttonSection}
|
||||
/>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
BaseBox,
|
||||
BoxButtonOnFooter,
|
||||
@@ -5,38 +6,149 @@ import {
|
||||
ButtonCustom,
|
||||
CenterCustom,
|
||||
InformationBox,
|
||||
LoaderCustom,
|
||||
Spacing,
|
||||
StackCustom,
|
||||
TextCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import { MainColor } from "@/constants/color-palet";
|
||||
import { FontAwesome5 } from "@expo/vector-icons";
|
||||
import { router } from "expo-router";
|
||||
import DIRECTORY_ID from "@/constants/directory-id";
|
||||
import {
|
||||
apiInvestmentGetOne,
|
||||
apiInvestmentUpdateData,
|
||||
} from "@/service/api-client/api-investment";
|
||||
import { deleteFileService, uploadFileService } from "@/service/upload-service";
|
||||
import pickFile, { IFileData } 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";
|
||||
|
||||
export default function InvestmentEditProspectus() {
|
||||
const { id } = useLocalSearchParams();
|
||||
const [data, setData] = useState<any>(null);
|
||||
const [loadingGet, setLoadingGet] = useState(false);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [pdf, setPdf] = useState<IFileData | null>(null);
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
onLoadData();
|
||||
}, [id])
|
||||
);
|
||||
|
||||
const onLoadData = async () => {
|
||||
try {
|
||||
setLoadingGet(true);
|
||||
const response = await apiInvestmentGetOne({
|
||||
id: id as string,
|
||||
});
|
||||
setData(response.data);
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
} finally {
|
||||
setLoadingGet(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSubmitUpdate = async () => {
|
||||
const prevProspectusFileId = data?.prospektusFileId;
|
||||
try {
|
||||
setIsLoading(true);
|
||||
|
||||
const responseUploadImage = await uploadFileService({
|
||||
imageUri: pdf?.uri as any,
|
||||
dirId: DIRECTORY_ID.investasi_prospektus,
|
||||
});
|
||||
|
||||
if (!responseUploadImage.success) {
|
||||
Toast.show({
|
||||
type: "error",
|
||||
text1: "Gagal mengunggah gambar",
|
||||
});
|
||||
return;
|
||||
}
|
||||
const prospektusFileId = responseUploadImage.data.id;
|
||||
|
||||
const responseUpdate = await apiInvestmentUpdateData({
|
||||
id: id as string,
|
||||
data: prospektusFileId,
|
||||
category: "prospectus",
|
||||
});
|
||||
|
||||
if (responseUpdate.success) {
|
||||
const deletePrevImage = await deleteFileService({
|
||||
id: prevProspectusFileId as any,
|
||||
});
|
||||
|
||||
if (!deletePrevImage.success) {
|
||||
console.log("[ERROR DELETE PREV IMAGE]", deletePrevImage.message);
|
||||
return;
|
||||
}
|
||||
|
||||
Toast.show({
|
||||
type: "success",
|
||||
text1: "Data berhasil diupdate",
|
||||
});
|
||||
router.back();
|
||||
} else {
|
||||
Toast.show({
|
||||
type: "error",
|
||||
text1: responseUpdate.message,
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const buttonFooter = (
|
||||
<BoxButtonOnFooter>
|
||||
<ButtonCustom onPress={() => router.back()}>Update</ButtonCustom>
|
||||
<ButtonCustom
|
||||
disabled={pdf === null}
|
||||
isLoading={isLoading}
|
||||
onPress={handleSubmitUpdate}
|
||||
>
|
||||
Update
|
||||
</ButtonCustom>
|
||||
</BoxButtonOnFooter>
|
||||
);
|
||||
return (
|
||||
<ViewWrapper footerComponent={buttonFooter}>
|
||||
<ViewWrapper footerComponent={!loadingGet && buttonFooter}>
|
||||
<StackCustom gap={"xs"}>
|
||||
<InformationBox text="File prospektus wajib untuk diupload, agar calon investor paham dengan prospek investasi yang akan anda jalankan kedepan." />
|
||||
<Spacing />
|
||||
|
||||
<BaseBox>
|
||||
<CenterCustom>
|
||||
<FontAwesome5
|
||||
name="file-pdf"
|
||||
size={30}
|
||||
color={MainColor.disabled}
|
||||
/>
|
||||
{loadingGet ? (
|
||||
<LoaderCustom />
|
||||
) : pdf ? (
|
||||
<TextCustom truncate>{pdf.name}</TextCustom>
|
||||
) : (
|
||||
<TextCustom truncate>
|
||||
{_.snakeCase(data?.title || "").replace(/_/g, "-")}.pdf
|
||||
</TextCustom>
|
||||
)}
|
||||
</CenterCustom>
|
||||
</BaseBox>
|
||||
<ButtonCenteredOnly
|
||||
disabled={loadingGet}
|
||||
icon="upload"
|
||||
onPress={() => router.push("/(application)/(image)/take-picture/123")}
|
||||
onPress={() => {
|
||||
pickFile({
|
||||
allowedType: "pdf",
|
||||
setPdfUri(file: any) {
|
||||
setPdf({
|
||||
uri: file.uri,
|
||||
name: file.name,
|
||||
size: file.size,
|
||||
});
|
||||
},
|
||||
});
|
||||
}}
|
||||
>
|
||||
Upload
|
||||
</ButtonCenteredOnly>
|
||||
|
||||
@@ -1,40 +1,224 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
ButtonCenteredOnly,
|
||||
ButtonCustom,
|
||||
InformationBox,
|
||||
LandscapeFrameUploaded,
|
||||
LoaderCustom,
|
||||
SelectCustom,
|
||||
Spacing,
|
||||
StackCustom,
|
||||
TextInputCustom,
|
||||
ViewWrapper
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import dummyPembagianDeviden from "@/lib/dummy-data/investment/pembagian-deviden";
|
||||
import dummyListPencarianInvestor from "@/lib/dummy-data/investment/pencarian-investor";
|
||||
import dummyPeriodeDeviden from "@/lib/dummy-data/investment/periode-deviden";
|
||||
import { router } from "expo-router";
|
||||
import { useState } from "react";
|
||||
import API_STRORAGE from "@/constants/base-url-api-strorage";
|
||||
import DIRECTORY_ID from "@/constants/directory-id";
|
||||
import {
|
||||
apiInvestmentGetOne,
|
||||
apiInvestmentUpdateData,
|
||||
} from "@/service/api-client/api-investment";
|
||||
import { apiMasterInvestment } from "@/service/api-client/api-master";
|
||||
import {
|
||||
deleteFileService,
|
||||
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 IInvestment {
|
||||
title?: string;
|
||||
targetDana?: string;
|
||||
hargaLembar?: string;
|
||||
totalLembar?: string;
|
||||
roi?: string;
|
||||
masterPencarianInvestorId?: string;
|
||||
masterPeriodeDevidenId?: string;
|
||||
masterPembagianDevidenId?: string;
|
||||
authorId?: string;
|
||||
imageId?: string;
|
||||
prospektusFileId?: string;
|
||||
}
|
||||
|
||||
export default function InvestmentEdit() {
|
||||
const [data, setData] = useState({
|
||||
const { id } = useLocalSearchParams();
|
||||
const [data, setData] = useState<IInvestment>({
|
||||
title: "",
|
||||
targetDana: 0,
|
||||
hargaPerLembar: 0,
|
||||
totalLembar: 0,
|
||||
rasioKeuntungan: 0,
|
||||
pencarianInvestor: "",
|
||||
periodeDeviden: "",
|
||||
pembagianDeviden: "",
|
||||
targetDana: "",
|
||||
hargaLembar: "",
|
||||
totalLembar: "",
|
||||
roi: "",
|
||||
masterPencarianInvestorId: "",
|
||||
masterPeriodeDevidenId: "",
|
||||
masterPembagianDevidenId: "",
|
||||
authorId: "",
|
||||
imageId: "",
|
||||
prospektusFileId: "",
|
||||
});
|
||||
|
||||
const [image, setImage] = useState<string | null>(null);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [loadingMaster, setLoadingMaster] = useState(false);
|
||||
const [listPencarianInvestor, setListPencarianInvestor] = useState<any[]>([]);
|
||||
const [listPeriodeDeviden, setListPeriodeDeviden] = useState<any[]>([]);
|
||||
const [listPembagianDeviden, setListPembagianDeviden] = useState<any[]>([]);
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
onLoadMaster();
|
||||
onLoadData();
|
||||
}, [id])
|
||||
);
|
||||
|
||||
const onLoadMaster = async () => {
|
||||
try {
|
||||
setLoadingMaster(true);
|
||||
const response = await apiMasterInvestment({ category: "" });
|
||||
|
||||
setListPencarianInvestor(response.data.pencarianInvestor);
|
||||
setListPeriodeDeviden(response.data.periodeDeviden);
|
||||
setListPembagianDeviden(response.data.pembagianDeviden);
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
} finally {
|
||||
setLoadingMaster(false);
|
||||
}
|
||||
};
|
||||
|
||||
const onLoadData = async () => {
|
||||
try {
|
||||
const response = await apiInvestmentGetOne({
|
||||
id: id as string,
|
||||
});
|
||||
setData(response.data);
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
}
|
||||
};
|
||||
|
||||
const displayTargetDana = formatCurrencyDisplay(data?.targetDana);
|
||||
const displayHargaPerLembar = formatCurrencyDisplay(data?.hargaLembar);
|
||||
const realTotalLembar = Number(data?.targetDana) / Number(data?.hargaLembar);
|
||||
const displayTotalLembar = formatCurrencyDisplay(realTotalLembar);
|
||||
|
||||
const handleChangeCurrency = (field: keyof typeof data) => (text: string) => {
|
||||
const numeric = text.replace(/\D/g, "");
|
||||
setData((prev) => ({ ...prev, [field]: numeric }));
|
||||
};
|
||||
|
||||
const validateData = () => {
|
||||
if (
|
||||
!data.title ||
|
||||
!data.targetDana ||
|
||||
!data.hargaLembar ||
|
||||
!data.totalLembar ||
|
||||
!data.roi ||
|
||||
!data.masterPencarianInvestorId ||
|
||||
!data.masterPeriodeDevidenId ||
|
||||
!data.masterPembagianDevidenId
|
||||
) {
|
||||
Toast.show({
|
||||
type: "info",
|
||||
text1: "Harap isi semua data",
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
const handleSubmitUpdate = async () => {
|
||||
let newData = {
|
||||
...data,
|
||||
totalLembar: realTotalLembar.toString(),
|
||||
};
|
||||
|
||||
if (!validateData()) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
setIsLoading(true);
|
||||
|
||||
if (image) {
|
||||
const responseUploadImage = await uploadFileService({
|
||||
imageUri: image,
|
||||
dirId: DIRECTORY_ID.investasi_image,
|
||||
});
|
||||
|
||||
if (!responseUploadImage.success) {
|
||||
Toast.show({
|
||||
type: "error",
|
||||
text1: "Gagal mengunggah gambar",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const deletePrevImage = await deleteFileService({
|
||||
id: data?.imageId as any,
|
||||
});
|
||||
|
||||
if (!deletePrevImage.success) {
|
||||
Toast.show({
|
||||
type: "error",
|
||||
text1: "Gagal menghapus gambar",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
newData = {
|
||||
...newData,
|
||||
imageId: responseUploadImage.data.id,
|
||||
};
|
||||
}
|
||||
|
||||
const responseUpdate = await apiInvestmentUpdateData({
|
||||
id: id as string,
|
||||
data: newData,
|
||||
category: "data"
|
||||
});
|
||||
|
||||
if (responseUpdate.success) {
|
||||
Toast.show({
|
||||
type: "success",
|
||||
text1: "Data berhasil diupdate",
|
||||
});
|
||||
router.back();
|
||||
} else {
|
||||
Toast.show({
|
||||
type: "error",
|
||||
text1: responseUpdate.message,
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<ViewWrapper>
|
||||
<StackCustom gap={"xs"}>
|
||||
<InformationBox text="Gambar investasi bisa berupa ilustrasi, poster atau foto terkait investasi." />
|
||||
<LandscapeFrameUploaded />
|
||||
<LandscapeFrameUploaded
|
||||
image={
|
||||
image ? image : API_STRORAGE.GET({ fileId: data?.imageId as any })
|
||||
}
|
||||
/>
|
||||
<ButtonCenteredOnly
|
||||
icon="upload"
|
||||
onPress={() => router.push("/take-picture/1")}
|
||||
onPress={() => {
|
||||
pickFile({
|
||||
setImageUri: ({ uri }) => {
|
||||
setImage(uri);
|
||||
},
|
||||
allowedType: "image",
|
||||
});
|
||||
}}
|
||||
>
|
||||
Upload
|
||||
</ButtonCenteredOnly>
|
||||
@@ -47,7 +231,7 @@ export default function InvestmentEdit() {
|
||||
required
|
||||
placeholder="Judul"
|
||||
label="Judul"
|
||||
value={data.title}
|
||||
value={data?.title}
|
||||
onChangeText={(value) => setData({ ...data, title: value })}
|
||||
/>
|
||||
|
||||
@@ -57,22 +241,8 @@ export default function InvestmentEdit() {
|
||||
placeholder="0"
|
||||
label="Target Dana"
|
||||
keyboardType="numeric"
|
||||
onChangeText={(value) =>
|
||||
setData({ ...data, targetDana: Number(value) })
|
||||
}
|
||||
value={data.targetDana === 0 ? "" : data.targetDana.toString()}
|
||||
/>
|
||||
|
||||
<TextInputCustom
|
||||
required
|
||||
iconLeft="Rp."
|
||||
placeholder="0"
|
||||
label="Target Dana"
|
||||
keyboardType="numeric"
|
||||
onChangeText={(value) =>
|
||||
setData({ ...data, targetDana: Number(value) })
|
||||
}
|
||||
value={data.targetDana === 0 ? "" : data.targetDana.toString()}
|
||||
onChangeText={handleChangeCurrency("targetDana")}
|
||||
value={displayTargetDana}
|
||||
/>
|
||||
|
||||
<TextInputCustom
|
||||
@@ -81,10 +251,8 @@ export default function InvestmentEdit() {
|
||||
placeholder="0"
|
||||
label="Harga Per Lembar"
|
||||
keyboardType="numeric"
|
||||
onChangeText={(value) =>
|
||||
setData({ ...data, targetDana: Number(value) })
|
||||
}
|
||||
value={data.targetDana === 0 ? "" : data.targetDana.toString()}
|
||||
onChangeText={handleChangeCurrency("hargaLembar")}
|
||||
value={displayHargaPerLembar}
|
||||
/>
|
||||
|
||||
<TextInputCustom
|
||||
@@ -92,10 +260,8 @@ export default function InvestmentEdit() {
|
||||
placeholder="0"
|
||||
label="Total Lembar"
|
||||
keyboardType="numeric"
|
||||
onChangeText={(value) =>
|
||||
setData({ ...data, totalLembar: Number(value) })
|
||||
}
|
||||
value={data.totalLembar === 0 ? "" : data.totalLembar.toString()}
|
||||
onChangeText={(value) => setData({ ...data, totalLembar: value })}
|
||||
value={displayTotalLembar}
|
||||
/>
|
||||
|
||||
<TextInputCustom
|
||||
@@ -104,57 +270,78 @@ export default function InvestmentEdit() {
|
||||
label="Rasio Keuntungan / ROI %"
|
||||
placeholder="0"
|
||||
keyboardType="numeric"
|
||||
onChangeText={(value) =>
|
||||
setData({ ...data, rasioKeuntungan: Number(value) })
|
||||
}
|
||||
value={
|
||||
data.rasioKeuntungan === 0 ? "" : data.rasioKeuntungan.toString()
|
||||
}
|
||||
onChangeText={(value) => setData({ ...data, roi: value })}
|
||||
value={data?.roi === "" ? "" : data?.roi}
|
||||
/>
|
||||
|
||||
<SelectCustom
|
||||
required
|
||||
placeholder="Pilih batas waktu"
|
||||
label="Pencarian Investor"
|
||||
data={dummyListPencarianInvestor.map((item) => ({
|
||||
label: item.name + `${" "}hari`,
|
||||
value: item.id,
|
||||
}))}
|
||||
onChange={(value) =>
|
||||
setData({ ...data, pencarianInvestor: value as any })
|
||||
}
|
||||
value={data.pencarianInvestor}
|
||||
/>
|
||||
{loadingMaster ? (
|
||||
<LoaderCustom />
|
||||
) : (
|
||||
<SelectCustom
|
||||
required
|
||||
placeholder="Pilih batas waktu"
|
||||
label="Pencarian Investor"
|
||||
data={
|
||||
_.isEmpty(listPencarianInvestor)
|
||||
? []
|
||||
: listPencarianInvestor.map((item) => ({
|
||||
label: item.name + `${" "}hari`,
|
||||
value: item.id,
|
||||
}))
|
||||
}
|
||||
onChange={(value) =>
|
||||
setData({ ...data, masterPencarianInvestorId: value as any })
|
||||
}
|
||||
value={data.masterPencarianInvestorId}
|
||||
/>
|
||||
)}
|
||||
|
||||
<SelectCustom
|
||||
required
|
||||
placeholder="Pilih batas waktu"
|
||||
label="Pilih Periode Deviden"
|
||||
data={dummyPeriodeDeviden.map((item) => ({
|
||||
label: item.name,
|
||||
value: item.id,
|
||||
}))}
|
||||
onChange={(value) =>
|
||||
setData({ ...data, periodeDeviden: value as any })
|
||||
}
|
||||
value={data.periodeDeviden}
|
||||
/>
|
||||
{loadingMaster ? (
|
||||
<LoaderCustom />
|
||||
) : (
|
||||
<SelectCustom
|
||||
required
|
||||
placeholder="Pilih batas waktu"
|
||||
label="Pilih Periode Deviden"
|
||||
data={
|
||||
_.isEmpty(listPeriodeDeviden)
|
||||
? []
|
||||
: listPeriodeDeviden.map((item) => ({
|
||||
label: item.name,
|
||||
value: item.id,
|
||||
}))
|
||||
}
|
||||
onChange={(value) =>
|
||||
setData({ ...data, masterPeriodeDevidenId: value as any })
|
||||
}
|
||||
value={data.masterPeriodeDevidenId}
|
||||
/>
|
||||
)}
|
||||
|
||||
{loadingMaster ? (
|
||||
<LoaderCustom />
|
||||
) : (
|
||||
<SelectCustom
|
||||
required
|
||||
placeholder="Pilih batas waktu"
|
||||
label="Pilih Pembagian Deviden"
|
||||
data={
|
||||
_.isEmpty(listPembagianDeviden)
|
||||
? []
|
||||
: listPembagianDeviden.map((item) => ({
|
||||
label: item.name + `${" "}bulan`,
|
||||
value: item.id,
|
||||
}))
|
||||
}
|
||||
onChange={(value) =>
|
||||
setData({ ...data, masterPembagianDevidenId: value as any })
|
||||
}
|
||||
value={data.masterPembagianDevidenId}
|
||||
/>
|
||||
)}
|
||||
|
||||
<SelectCustom
|
||||
required
|
||||
placeholder="Pilih batas waktu"
|
||||
label="Pilih Pembagian Deviden"
|
||||
data={dummyPembagianDeviden.map((item) => ({
|
||||
label: item.name + `${" "}bulan`,
|
||||
value: item.id,
|
||||
}))}
|
||||
onChange={(value) =>
|
||||
setData({ ...data, pembagianDeviden: value as any })
|
||||
}
|
||||
value={data.pembagianDeviden}
|
||||
/>
|
||||
<Spacing />
|
||||
<ButtonCustom onPress={() => router.replace("/investment/portofolio")}>
|
||||
<ButtonCustom isLoading={isLoading} onPress={handleSubmitUpdate}>
|
||||
Simpan
|
||||
</ButtonCustom>
|
||||
</StackCustom>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
BackButton,
|
||||
DotButton,
|
||||
@@ -9,18 +10,45 @@ import { IconDocument, IconEdit, IconNews } from "@/components/_Icon";
|
||||
import { IMenuDrawerItem } from "@/components/_Interface/types";
|
||||
import { MainColor } from "@/constants/color-palet";
|
||||
import { ICON_SIZE_MEDIUM } from "@/constants/constans-value";
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
import Investment_ButtonInvestasiSection from "@/screens/Invesment/ButtonInvestasiSection";
|
||||
import Invesment_ComponentBoxOnBottomDetail from "@/screens/Invesment/ComponentBoxOnBottomDetail";
|
||||
import Invesment_DetailDataPublishSection from "@/screens/Invesment/DetailDataPublishSection";
|
||||
import { apiInvestmentGetOne } from "@/service/api-client/api-investment";
|
||||
import { AntDesign, MaterialIcons } from "@expo/vector-icons";
|
||||
import { router, Stack, useLocalSearchParams } from "expo-router";
|
||||
import {
|
||||
router,
|
||||
Stack,
|
||||
useFocusEffect,
|
||||
useLocalSearchParams,
|
||||
} from "expo-router";
|
||||
import _ from "lodash";
|
||||
import { useState } from "react";
|
||||
import { useCallback, useState } from "react";
|
||||
|
||||
export default function InvestmentDetail() {
|
||||
const { user } = useAuth();
|
||||
const { id, status } = useLocalSearchParams();
|
||||
const [openDrawerDraft, setOpenDrawerDraft] = useState(false);
|
||||
const [openDrawerPublish, setOpenDrawerPublish] = useState(false);
|
||||
const [data, setData] = useState<any>(null);
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
onLoadData();
|
||||
}, [id, status])
|
||||
);
|
||||
|
||||
const onLoadData = async () => {
|
||||
try {
|
||||
const response = await apiInvestmentGetOne({
|
||||
id: id as string,
|
||||
});
|
||||
|
||||
setData(response.data);
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
}
|
||||
};
|
||||
|
||||
const handlePressDraft = (item: IMenuDrawerItem) => {
|
||||
console.log("PATH >> ", item.path);
|
||||
@@ -37,11 +65,14 @@ export default function InvestmentDetail() {
|
||||
const bottomSection = (
|
||||
<Invesment_ComponentBoxOnBottomDetail
|
||||
id={id as string}
|
||||
status={'publish'}
|
||||
prospectusId={data?.prospektusFileId}
|
||||
status={"publish"}
|
||||
/>
|
||||
);
|
||||
|
||||
const buttonSection = <Investment_ButtonInvestasiSection id={id as string} isMine={true} />;
|
||||
const buttonSection = (
|
||||
<Investment_ButtonInvestasiSection id={id as string} isMine={user?.id === data?.author?.id} />
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -61,6 +92,7 @@ export default function InvestmentDetail() {
|
||||
<ViewWrapper>
|
||||
<Invesment_DetailDataPublishSection
|
||||
status={"publish"}
|
||||
data={data}
|
||||
bottomSection={bottomSection}
|
||||
buttonSection={buttonSection}
|
||||
/>
|
||||
|
||||
@@ -1,20 +1,66 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
AvatarUsernameAndOtherComponent,
|
||||
BoxWithHeaderSection,
|
||||
LoaderCustom,
|
||||
TextCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import NoDataText from "@/components/_ShareComponent/NoDataText";
|
||||
import { apiInvestmentGetInvestorById } from "@/service/api-client/api-investment";
|
||||
import { formatCurrencyDisplay } from "@/utils/formatCurrencyDisplay";
|
||||
import { useFocusEffect, useLocalSearchParams } from "expo-router";
|
||||
import _ from "lodash";
|
||||
import { useCallback, useState } from "react";
|
||||
|
||||
export default function InvestmentInvestor() {
|
||||
const { id } = useLocalSearchParams();
|
||||
const [list, setList] = useState<any[] | null>(null);
|
||||
const [loadingList, setLoadingList] = useState(false);
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
onLoadList();
|
||||
}, [id])
|
||||
);
|
||||
|
||||
const onLoadList = async () => {
|
||||
try {
|
||||
setLoadingList(true);
|
||||
const response = await apiInvestmentGetInvestorById({
|
||||
id: id as string,
|
||||
})
|
||||
console.log("[DATA LIST]", JSON.stringify(response.data, null, 2));
|
||||
setList(response.data);
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
} finally {
|
||||
setLoadingList(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
<ViewWrapper>
|
||||
{Array.from({ length: 10 }).map((_, index) => (
|
||||
<BoxWithHeaderSection key={index}>
|
||||
<AvatarUsernameAndOtherComponent />
|
||||
<TextCustom bold>Rp. 7.000.000</TextCustom>
|
||||
</BoxWithHeaderSection>
|
||||
))}
|
||||
{loadingList ? (
|
||||
<LoaderCustom />
|
||||
) : _.isEmpty(list) ? (
|
||||
<NoDataText />
|
||||
) : (
|
||||
list?.map((item: any, index: number) => (
|
||||
<BoxWithHeaderSection key={index}>
|
||||
<AvatarUsernameAndOtherComponent
|
||||
avatar={item?.Author?.Profile?.imageId}
|
||||
name={item?.Author?.username}
|
||||
avatarHref={`/profile/${item?.Author?.Profile?.id}`}
|
||||
/>
|
||||
<TextCustom bold>
|
||||
Rp. {formatCurrencyDisplay(item?.nominal)}
|
||||
</TextCustom>
|
||||
</BoxWithHeaderSection>
|
||||
))
|
||||
)}
|
||||
</ViewWrapper>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -1,76 +1,235 @@
|
||||
import {
|
||||
BaseBox,
|
||||
ButtonCenteredOnly,
|
||||
ButtonCustom,
|
||||
CenterCustom,
|
||||
InformationBox,
|
||||
LandscapeFrameUploaded,
|
||||
SelectCustom,
|
||||
Spacing,
|
||||
StackCustom,
|
||||
TextInputCustom,
|
||||
ViewWrapper,
|
||||
BaseBox,
|
||||
ButtonCenteredOnly,
|
||||
ButtonCustom,
|
||||
CenterCustom,
|
||||
InformationBox,
|
||||
LandscapeFrameUploaded,
|
||||
LoaderCustom,
|
||||
SelectCustom,
|
||||
Spacing,
|
||||
StackCustom,
|
||||
TextCustom,
|
||||
TextInputCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import { MainColor } from "@/constants/color-palet";
|
||||
import dummyPembagianDeviden from "@/lib/dummy-data/investment/pembagian-deviden";
|
||||
import dummyListPencarianInvestor from "@/lib/dummy-data/investment/pencarian-investor";
|
||||
import dummyPeriodeDeviden from "@/lib/dummy-data/investment/periode-deviden";
|
||||
import DIRECTORY_ID from "@/constants/directory-id";
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
import { apiInvestmentCreate } from "@/service/api-client/api-investment";
|
||||
import { apiMasterInvestment } from "@/service/api-client/api-master";
|
||||
import { uploadFileService } from "@/service/upload-service";
|
||||
import { formatCurrencyDisplay } from "@/utils/formatCurrencyDisplay";
|
||||
import pickFile, { IFileData } from "@/utils/pickFile";
|
||||
import { FontAwesome5 } from "@expo/vector-icons";
|
||||
import { router } from "expo-router";
|
||||
import { useState } from "react";
|
||||
import { router, useFocusEffect } from "expo-router";
|
||||
import _ from "lodash";
|
||||
import { useCallback, useState } from "react";
|
||||
import Toast from "react-native-toast-message";
|
||||
|
||||
export default function InvestmentCreate() {
|
||||
const { user } = useAuth();
|
||||
const [data, setData] = useState({
|
||||
title: "",
|
||||
targetDana: 0,
|
||||
hargaPerLembar: 0,
|
||||
totalLembar: 0,
|
||||
rasioKeuntungan: 0,
|
||||
targetDana: "",
|
||||
hargaPerLembar: "",
|
||||
totalLembar: "",
|
||||
rasioKeuntungan: "",
|
||||
pencarianInvestor: "",
|
||||
periodeDeviden: "",
|
||||
pembagianDeviden: "",
|
||||
authorId: "",
|
||||
imageId: "",
|
||||
prospektusFileId: "",
|
||||
});
|
||||
const [image, setImage] = useState<string | null>(null);
|
||||
const [pdf, setPdf] = useState<IFileData | null>(null);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
// const [coba, setCoba] = useState("");
|
||||
const [loadingMaster, setLoadingMaster] = useState(false);
|
||||
const [listPencarianInvestor, setListPencarianInvestor] = useState<any[]>([]);
|
||||
const [listPeriodeDeviden, setListPeriodeDeviden] = useState<any[]>([]);
|
||||
const [listPembagianDeviden, setListPembagianDeviden] = useState<any[]>([]);
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
onLoadMaster();
|
||||
}, [])
|
||||
);
|
||||
|
||||
const onLoadMaster = async () => {
|
||||
try {
|
||||
setLoadingMaster(true);
|
||||
const response = await apiMasterInvestment({ category: "" });
|
||||
|
||||
setListPencarianInvestor(response.data.pencarianInvestor);
|
||||
setListPeriodeDeviden(response.data.periodeDeviden);
|
||||
setListPembagianDeviden(response.data.pembagianDeviden);
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
} finally {
|
||||
setLoadingMaster(false);
|
||||
}
|
||||
};
|
||||
|
||||
const displayTargetDana = formatCurrencyDisplay(data.targetDana);
|
||||
const displayHargaPerLembar = formatCurrencyDisplay(data.hargaPerLembar);
|
||||
const realTotalLembar = Number(data.targetDana) / Number(data.hargaPerLembar);
|
||||
const displayTotalLembar = formatCurrencyDisplay(realTotalLembar);
|
||||
|
||||
const handleChangeCurrency = (field: keyof typeof data) => (text: string) => {
|
||||
const numeric = text.replace(/\D/g, "");
|
||||
setData((prev) => ({ ...prev, [field]: numeric }));
|
||||
};
|
||||
|
||||
const validateData = () => {
|
||||
if (
|
||||
!data.title ||
|
||||
!data.targetDana ||
|
||||
!data.hargaPerLembar ||
|
||||
!data.rasioKeuntungan ||
|
||||
!data.pencarianInvestor ||
|
||||
!data.periodeDeviden ||
|
||||
!data.pembagianDeviden
|
||||
) {
|
||||
Toast.show({
|
||||
type: "error",
|
||||
text1: "Harap isi semua data",
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
const handleSubmit = async () => {
|
||||
if (!validateData()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!image || !pdf) {
|
||||
Toast.show({
|
||||
type: "error",
|
||||
text1: "Harap upload gambar dan file PDF",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
setIsLoading(true);
|
||||
const responseUploadImage = await uploadFileService({
|
||||
imageUri: image,
|
||||
dirId: DIRECTORY_ID.investasi_image,
|
||||
});
|
||||
|
||||
if (!responseUploadImage.success) {
|
||||
Toast.show({
|
||||
type: "error",
|
||||
text1: "Gagal mengunggah gambar",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const imageId = responseUploadImage.data.id;
|
||||
const responseUploadPdf = await uploadFileService({
|
||||
imageUri: pdf.uri,
|
||||
dirId: DIRECTORY_ID.investasi_prospektus,
|
||||
});
|
||||
|
||||
if (!responseUploadPdf.success) {
|
||||
Toast.show({
|
||||
type: "error",
|
||||
text1: "Gagal mengunggah file PDF",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const pdfId = responseUploadPdf.data.id;
|
||||
const newData = {
|
||||
title: data.title,
|
||||
targetDana: data.targetDana,
|
||||
hargaLembar: data.hargaPerLembar,
|
||||
totalLembar: realTotalLembar.toString(),
|
||||
roi: data.rasioKeuntungan,
|
||||
masterPencarianInvestorId: data.pencarianInvestor,
|
||||
masterPembagianDevidenId: data.pembagianDeviden,
|
||||
masterPeriodeDevidenId: data.periodeDeviden,
|
||||
authorId: user?.id,
|
||||
imageId: imageId,
|
||||
prospektusFileId: pdfId,
|
||||
};
|
||||
|
||||
const response = await apiInvestmentCreate({ data: newData });
|
||||
|
||||
if (response.success) {
|
||||
Toast.show({
|
||||
type: "success",
|
||||
text1: "Berhasil",
|
||||
text2: response.message,
|
||||
});
|
||||
router.replace("/investment/portofolio");
|
||||
} else {
|
||||
Toast.show({
|
||||
type: "error",
|
||||
text1: "Info",
|
||||
text2: response.message,
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("error", error);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
// const [coba, setCoba] = useState("");
|
||||
return (
|
||||
<ViewWrapper>
|
||||
<StackCustom gap={"xs"}>
|
||||
{/* <View style={GStyles.inputContainerInput}>
|
||||
<TextInput
|
||||
style={{
|
||||
...GStyles.inputText,
|
||||
}}
|
||||
onChangeText={(value) => setCoba(value)}
|
||||
value={coba}
|
||||
keyboardType="decimal-pad"
|
||||
/>
|
||||
</View> */}
|
||||
|
||||
<InformationBox text="Gambar investasi bisa berupa ilustrasi, poster atau foto terkait investasi." />
|
||||
<LandscapeFrameUploaded />
|
||||
<LandscapeFrameUploaded image={image as string} />
|
||||
<ButtonCenteredOnly
|
||||
icon="upload"
|
||||
onPress={() => router.push("/take-picture/1")}
|
||||
onPress={() => {
|
||||
pickFile({
|
||||
setImageUri: ({ uri }) => {
|
||||
setImage(uri);
|
||||
},
|
||||
allowedType: "image",
|
||||
});
|
||||
}}
|
||||
>
|
||||
Upload
|
||||
</ButtonCenteredOnly>
|
||||
|
||||
<Spacing />
|
||||
|
||||
<InformationBox text="File prospektus wajib untuk diupload, agar calon investor paham dengan prospek investasi yang akan anda jalankan kedepannya." />
|
||||
<InformationBox text="File prospektus wajib untuk diupload, agar calon investor paham dengan prospek investasi yang akan anda jalankan kedepannya. Gunakan format PDF." />
|
||||
|
||||
<BaseBox>
|
||||
<CenterCustom>
|
||||
<FontAwesome5
|
||||
name="file-pdf"
|
||||
size={30}
|
||||
color={MainColor.disabled}
|
||||
/>
|
||||
{pdf ? (
|
||||
<TextCustom>{pdf.name}</TextCustom>
|
||||
) : (
|
||||
<FontAwesome5
|
||||
name="file-pdf"
|
||||
size={30}
|
||||
color={MainColor.disabled}
|
||||
/>
|
||||
)}
|
||||
</CenterCustom>
|
||||
</BaseBox>
|
||||
<ButtonCenteredOnly
|
||||
icon="upload"
|
||||
onPress={() => router.push("/take-picture/1")}
|
||||
onPress={() => {
|
||||
pickFile({
|
||||
setPdfUri: ({ uri, name, size }) => {
|
||||
|
||||
setPdf({ uri, name, size });
|
||||
},
|
||||
allowedType: "pdf",
|
||||
});
|
||||
}}
|
||||
>
|
||||
Upload File
|
||||
</ButtonCenteredOnly>
|
||||
@@ -90,22 +249,8 @@ export default function InvestmentCreate() {
|
||||
placeholder="0"
|
||||
label="Target Dana"
|
||||
keyboardType="numeric"
|
||||
onChangeText={(value) =>
|
||||
setData({ ...data, targetDana: Number(value) })
|
||||
}
|
||||
value={data.targetDana === 0 ? "" : data.targetDana.toString()}
|
||||
/>
|
||||
|
||||
<TextInputCustom
|
||||
required
|
||||
iconLeft="Rp."
|
||||
placeholder="0"
|
||||
label="Target Dana"
|
||||
keyboardType="numeric"
|
||||
onChangeText={(value) =>
|
||||
setData({ ...data, targetDana: Number(value) })
|
||||
}
|
||||
value={data.targetDana === 0 ? "" : data.targetDana.toString()}
|
||||
onChangeText={handleChangeCurrency("targetDana")}
|
||||
value={displayTargetDana}
|
||||
/>
|
||||
|
||||
<TextInputCustom
|
||||
@@ -114,22 +259,24 @@ export default function InvestmentCreate() {
|
||||
placeholder="0"
|
||||
label="Harga Per Lembar"
|
||||
keyboardType="numeric"
|
||||
onChangeText={(value) =>
|
||||
setData({ ...data, targetDana: Number(value) })
|
||||
}
|
||||
value={data.targetDana === 0 ? "" : data.targetDana.toString()}
|
||||
onChangeText={handleChangeCurrency("hargaPerLembar")}
|
||||
value={displayHargaPerLembar}
|
||||
/>
|
||||
|
||||
<TextInputCustom
|
||||
required
|
||||
placeholder="0"
|
||||
label="Total Lembar"
|
||||
keyboardType="numeric"
|
||||
onChangeText={(value) =>
|
||||
setData({ ...data, totalLembar: Number(value) })
|
||||
}
|
||||
value={data.totalLembar === 0 ? "" : data.totalLembar.toString()}
|
||||
/>
|
||||
<StackCustom gap={0}>
|
||||
<TextInputCustom
|
||||
required
|
||||
placeholder="0"
|
||||
label="Total Lembar"
|
||||
keyboardType="numeric"
|
||||
// onChangeText={handleChangeCurrency("totalLembar")}
|
||||
value={displayTotalLembar}
|
||||
/>
|
||||
<TextCustom size={"small"} color="gray">
|
||||
*Total lembar dihitung dari, Target Dana / Harga Perlembar
|
||||
</TextCustom>
|
||||
</StackCustom>
|
||||
<Spacing />
|
||||
|
||||
<TextInputCustom
|
||||
required
|
||||
@@ -137,57 +284,80 @@ export default function InvestmentCreate() {
|
||||
label="Rasio Keuntungan / ROI %"
|
||||
placeholder="0"
|
||||
keyboardType="numeric"
|
||||
onChangeText={(value) =>
|
||||
setData({ ...data, rasioKeuntungan: Number(value) })
|
||||
}
|
||||
onChangeText={(value) => setData({ ...data, rasioKeuntungan: value })}
|
||||
value={
|
||||
data.rasioKeuntungan === 0 ? "" : data.rasioKeuntungan.toString()
|
||||
data.rasioKeuntungan === "" ? "" : data.rasioKeuntungan.toString()
|
||||
}
|
||||
/>
|
||||
|
||||
<SelectCustom
|
||||
required
|
||||
placeholder="Pilih batas waktu"
|
||||
label="Pencarian Investor"
|
||||
data={dummyListPencarianInvestor.map((item) => ({
|
||||
label: item.name + `${" "}hari`,
|
||||
value: item.id,
|
||||
}))}
|
||||
onChange={(value) =>
|
||||
setData({ ...data, pencarianInvestor: value as any })
|
||||
}
|
||||
value={data.pencarianInvestor}
|
||||
/>
|
||||
{loadingMaster ? (
|
||||
<LoaderCustom />
|
||||
) : (
|
||||
<SelectCustom
|
||||
required
|
||||
placeholder="Pilih batas waktu"
|
||||
label="Pencarian Investor"
|
||||
data={
|
||||
_.isEmpty(listPencarianInvestor)
|
||||
? []
|
||||
: listPencarianInvestor.map((item) => ({
|
||||
label: item.name + `${" "}hari`,
|
||||
value: item.id,
|
||||
}))
|
||||
}
|
||||
onChange={(value) =>
|
||||
setData({ ...data, pencarianInvestor: value as any })
|
||||
}
|
||||
value={data.pencarianInvestor}
|
||||
/>
|
||||
)}
|
||||
|
||||
<SelectCustom
|
||||
required
|
||||
placeholder="Pilih batas waktu"
|
||||
label="Pilih Periode Deviden"
|
||||
data={dummyPeriodeDeviden.map((item) => ({
|
||||
label: item.name,
|
||||
value: item.id,
|
||||
}))}
|
||||
onChange={(value) =>
|
||||
setData({ ...data, periodeDeviden: value as any })
|
||||
}
|
||||
value={data.periodeDeviden}
|
||||
/>
|
||||
{loadingMaster ? (
|
||||
<LoaderCustom />
|
||||
) : (
|
||||
<SelectCustom
|
||||
required
|
||||
placeholder="Pilih batas waktu"
|
||||
label="Pilih Periode Deviden"
|
||||
data={
|
||||
_.isEmpty(listPeriodeDeviden)
|
||||
? []
|
||||
: listPeriodeDeviden.map((item) => ({
|
||||
label: item.name,
|
||||
value: item.id,
|
||||
}))
|
||||
}
|
||||
onChange={(value) =>
|
||||
setData({ ...data, periodeDeviden: value as any })
|
||||
}
|
||||
value={data.periodeDeviden}
|
||||
/>
|
||||
)}
|
||||
|
||||
{loadingMaster ? (
|
||||
<LoaderCustom />
|
||||
) : (
|
||||
<SelectCustom
|
||||
required
|
||||
placeholder="Pilih batas waktu"
|
||||
label="Pilih Pembagian Deviden"
|
||||
data={
|
||||
_.isEmpty(listPembagianDeviden)
|
||||
? []
|
||||
: listPembagianDeviden.map((item) => ({
|
||||
label: item.name + `${" "}bulan`,
|
||||
value: item.id,
|
||||
}))
|
||||
}
|
||||
onChange={(value) =>
|
||||
setData({ ...data, pembagianDeviden: value as any })
|
||||
}
|
||||
value={data.pembagianDeviden}
|
||||
/>
|
||||
)}
|
||||
|
||||
<SelectCustom
|
||||
required
|
||||
placeholder="Pilih batas waktu"
|
||||
label="Pilih Pembagian Deviden"
|
||||
data={dummyPembagianDeviden.map((item) => ({
|
||||
label: item.name + `${" "}bulan`,
|
||||
value: item.id,
|
||||
}))}
|
||||
onChange={(value) =>
|
||||
setData({ ...data, pembagianDeviden: value as any })
|
||||
}
|
||||
value={data.pembagianDeviden}
|
||||
/>
|
||||
<Spacing />
|
||||
<ButtonCustom onPress={() => router.replace("/investment/portofolio")}>
|
||||
<ButtonCustom isLoading={isLoading} onPress={() => handleSubmit()}>
|
||||
Simpan
|
||||
</ButtonCustom>
|
||||
</StackCustom>
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
} from "@/components";
|
||||
import { IconEdit } from "@/components/_Icon";
|
||||
import { IMenuDrawerItem } from "@/components/_Interface/types";
|
||||
import ReportBox from "@/components/Box/ReportBox";
|
||||
import Job_BoxDetailSection from "@/screens/Job/BoxDetailSection";
|
||||
import Job_ButtonStatusSection from "@/screens/Job/ButtonStatusSection";
|
||||
import { apiJobGetOne } from "@/service/api-client/api-job";
|
||||
@@ -70,7 +71,13 @@ export default function JobDetailStatus() {
|
||||
<LoaderCustom />
|
||||
) : (
|
||||
<>
|
||||
<StackCustom>
|
||||
<StackCustom gap={"xs"}>
|
||||
{data &&
|
||||
data?.catatan &&
|
||||
(status === "draft" || status === "rejected") && (
|
||||
<ReportBox text={data?.catatan} />
|
||||
)}
|
||||
|
||||
<Job_BoxDetailSection data={data} />
|
||||
<Job_ButtonStatusSection
|
||||
id={id as string}
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
BaseBox,
|
||||
ButtonCenteredOnly,
|
||||
ButtonCustom,
|
||||
DummyLandscapeImage,
|
||||
InformationBox,
|
||||
LandscapeFrameUploaded,
|
||||
LoaderCustom,
|
||||
Spacing,
|
||||
StackCustom,
|
||||
TextAreaCustom,
|
||||
TextInputCustom,
|
||||
ViewWrapper,
|
||||
BaseBox,
|
||||
ButtonCenteredOnly,
|
||||
ButtonCustom,
|
||||
DummyLandscapeImage,
|
||||
InformationBox,
|
||||
LandscapeFrameUploaded,
|
||||
LoaderCustom,
|
||||
Spacing,
|
||||
StackCustom,
|
||||
TextAreaCustom,
|
||||
TextInputCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import DIRECTORY_ID from "@/constants/directory-id";
|
||||
import { apiJobGetOne, apiJobUpdateData } from "@/service/api-client/api-job";
|
||||
import {
|
||||
deleteImageService,
|
||||
uploadImageService,
|
||||
deleteFileService,
|
||||
uploadFileService,
|
||||
} from "@/service/upload-service";
|
||||
import pickImage from "@/utils/pickImage";
|
||||
import { router, useLocalSearchParams } from "expo-router";
|
||||
@@ -69,7 +69,7 @@ export default function JobEdit() {
|
||||
let newImageId = "";
|
||||
|
||||
if (imageUri) {
|
||||
const responseUploadImage = await uploadImageService({
|
||||
const responseUploadImage = await uploadFileService({
|
||||
imageUri: imageUri,
|
||||
dirId: DIRECTORY_ID.job_image,
|
||||
});
|
||||
@@ -80,7 +80,7 @@ export default function JobEdit() {
|
||||
}
|
||||
|
||||
if (data?.imageId) {
|
||||
const responseDeleteImage = await deleteImageService({
|
||||
const responseDeleteImage = await deleteFileService({
|
||||
id: data.imageId,
|
||||
});
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
import DIRECTORY_ID from "@/constants/directory-id";
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
import { apiJobCreate } from "@/service/api-client/api-job";
|
||||
import { uploadImageService } from "@/service/upload-service";
|
||||
import { uploadFileService } from "@/service/upload-service";
|
||||
import pickImage from "@/utils/pickImage";
|
||||
import { router } from "expo-router";
|
||||
import { useState } from "react";
|
||||
@@ -66,7 +66,7 @@ export default function JobCreate() {
|
||||
return;
|
||||
}
|
||||
|
||||
const responseUploadImage = await uploadImageService({
|
||||
const responseUploadImage = await uploadFileService({
|
||||
imageUri: image,
|
||||
dirId: DIRECTORY_ID.job_image,
|
||||
});
|
||||
|
||||
@@ -1,54 +1,226 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
BaseBox,
|
||||
BoxButtonOnFooter,
|
||||
ButtonCenteredOnly,
|
||||
ButtonCustom,
|
||||
InformationBox,
|
||||
LandscapeFrameUploaded,
|
||||
MapCustom,
|
||||
Spacing,
|
||||
TextInputCustom,
|
||||
ViewWrapper
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import { router, useLocalSearchParams } from "expo-router";
|
||||
import API_IMAGE from "@/constants/api-storage";
|
||||
import DIRECTORY_ID from "@/constants/directory-id";
|
||||
import { apiMapsGetOne, apiMapsUpdate } from "@/service/api-client/api-maps";
|
||||
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 { StyleSheet, View } from "react-native";
|
||||
import MapView, { LatLng, Marker } from "react-native-maps";
|
||||
import Toast from "react-native-toast-message";
|
||||
|
||||
const defaultRegion = {
|
||||
latitude: -8.737109,
|
||||
longitude: 115.1756897,
|
||||
latitudeDelta: 0.1,
|
||||
longitudeDelta: 0.1,
|
||||
};
|
||||
export default function MapsEdit() {
|
||||
const { id } = useLocalSearchParams();
|
||||
const [data, setData] = useState<any | null>({
|
||||
id: "",
|
||||
namePin: "",
|
||||
latitude: "",
|
||||
longitude: "",
|
||||
imageId: "",
|
||||
});
|
||||
const [selectedLocation, setSelectedLocation] = useState<LatLng | null>(null);
|
||||
const [image, setImage] = useState<IFileData | null>(null);
|
||||
const [isLoading, setLoading] = useState(false);
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
onLoadData();
|
||||
}, [id])
|
||||
);
|
||||
|
||||
const onLoadData = async () => {
|
||||
try {
|
||||
const response = await apiMapsGetOne({ id: id as string });
|
||||
|
||||
if (response.success) {
|
||||
setData({
|
||||
id: response.data.id,
|
||||
namePin: response.data.namePin,
|
||||
latitude: response.data.latitude,
|
||||
longitude: response.data.longitude,
|
||||
imageId: response.data.imageId,
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleMapPress = (event: any) => {
|
||||
const { latitude, longitude } = event.nativeEvent.coordinate;
|
||||
const location = { latitude, longitude };
|
||||
setSelectedLocation(location);
|
||||
};
|
||||
|
||||
const handleSubmit = async () => {
|
||||
let newData: any;
|
||||
if (!data.namePin) {
|
||||
Toast.show({
|
||||
type: "error",
|
||||
text1: "Nama pin harus diisi",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
newData = {
|
||||
namePin: data?.namePin,
|
||||
latitude: selectedLocation?.latitude || data?.latitude,
|
||||
longitude: selectedLocation?.longitude || data?.longitude,
|
||||
};
|
||||
|
||||
try {
|
||||
setLoading(true);
|
||||
if (image) {
|
||||
const responseUpload = await uploadFileService({
|
||||
dirId: DIRECTORY_ID.map_image,
|
||||
imageUri: image?.uri,
|
||||
});
|
||||
|
||||
if (!responseUpload?.data?.id) {
|
||||
Toast.show({
|
||||
type: "error",
|
||||
text1: "Gagal mengunggah gambar",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const imageId = responseUpload?.data?.id;
|
||||
|
||||
newData = {
|
||||
namePin: data?.namePin,
|
||||
latitude: selectedLocation?.latitude,
|
||||
longitude: selectedLocation?.longitude,
|
||||
newImageId: imageId,
|
||||
};
|
||||
}
|
||||
|
||||
const responseUpdate = await apiMapsUpdate({
|
||||
id: data?.id,
|
||||
data: newData,
|
||||
});
|
||||
|
||||
if (!responseUpdate.success) {
|
||||
Toast.show({
|
||||
type: "error",
|
||||
text1: "Gagal mengupdate map",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
Toast.show({
|
||||
type: "success",
|
||||
text1: "Map berhasil diupdate",
|
||||
});
|
||||
router.back();
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const buttonFooter = (
|
||||
<BoxButtonOnFooter>
|
||||
<ButtonCustom
|
||||
onPress={() => {
|
||||
console.log(`Simpan maps ${id}`);
|
||||
router.back()
|
||||
}}
|
||||
disabled={!data.namePin}
|
||||
onPress={handleSubmit}
|
||||
isLoading={isLoading}
|
||||
>
|
||||
Simpan
|
||||
Update
|
||||
</ButtonCustom>
|
||||
</BoxButtonOnFooter>
|
||||
);
|
||||
|
||||
return (
|
||||
<ViewWrapper footerComponent={buttonFooter}>
|
||||
<InformationBox text="Tentukan lokasi pin map dengan menekan pada map." />
|
||||
|
||||
<BaseBox>
|
||||
<MapCustom />
|
||||
</BaseBox>
|
||||
<View style={[styles.container, { height: 400 }]}>
|
||||
<MapView
|
||||
style={styles.map}
|
||||
initialRegion={
|
||||
data?.latitude && data?.longitude
|
||||
? {
|
||||
latitude: data?.latitude,
|
||||
longitude: data?.longitude,
|
||||
latitudeDelta: 0.1,
|
||||
longitudeDelta: 0.1,
|
||||
}
|
||||
: defaultRegion
|
||||
}
|
||||
onPress={handleMapPress}
|
||||
showsUserLocation={true}
|
||||
showsMyLocationButton={true}
|
||||
loadingEnabled={true}
|
||||
loadingIndicatorColor="#666"
|
||||
loadingBackgroundColor="#f0f0f0"
|
||||
>
|
||||
{selectedLocation ? (
|
||||
<Marker
|
||||
coordinate={selectedLocation}
|
||||
title="Lokasi Dipilih"
|
||||
description={`Lat: ${selectedLocation.latitude.toFixed(
|
||||
6
|
||||
)}, Lng: ${selectedLocation.longitude.toFixed(6)}`}
|
||||
pinColor="red"
|
||||
/>
|
||||
) : (
|
||||
<Marker
|
||||
coordinate={defaultRegion}
|
||||
title="Lokasi Dipilih"
|
||||
description={`Lat: ${defaultRegion.latitude.toFixed(
|
||||
6
|
||||
)}, Lng: ${defaultRegion.longitude.toFixed(6)}`}
|
||||
pinColor="red"
|
||||
/>
|
||||
)}
|
||||
</MapView>
|
||||
</View>
|
||||
|
||||
<TextInputCustom
|
||||
required
|
||||
label="Nama Pin"
|
||||
placeholder="Masukkan nama pin maps"
|
||||
value={data?.namePin}
|
||||
onChangeText={(value) => setData({ ...data, namePin: value })}
|
||||
/>
|
||||
|
||||
<Spacing />
|
||||
|
||||
<InformationBox text="Upload foto lokasi bisnis anda untuk ditampilkan dalam detail maps." />
|
||||
<LandscapeFrameUploaded />
|
||||
<LandscapeFrameUploaded
|
||||
image={
|
||||
image
|
||||
? image?.uri
|
||||
: API_IMAGE.GET({ fileId: data?.imageId as string })
|
||||
}
|
||||
/>
|
||||
<ButtonCenteredOnly
|
||||
icon="upload"
|
||||
onPress={() => {
|
||||
console.log("Upload foto ");
|
||||
router.navigate(`/take-picture/${id}`);
|
||||
pickFile({
|
||||
allowedType: "image",
|
||||
setImageUri(file) {
|
||||
setImage(file);
|
||||
},
|
||||
});
|
||||
}}
|
||||
>
|
||||
Upload
|
||||
@@ -57,3 +229,16 @@ export default function MapsEdit() {
|
||||
</ViewWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
width: "100%",
|
||||
backgroundColor: "#f5f5f5",
|
||||
overflow: "hidden",
|
||||
borderRadius: 8,
|
||||
marginBottom: 20,
|
||||
},
|
||||
map: {
|
||||
flex: 1,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -6,21 +6,96 @@ import {
|
||||
InformationBox,
|
||||
LandscapeFrameUploaded,
|
||||
Spacing,
|
||||
TextCustom,
|
||||
TextInputCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import MapSelected from "@/components/Map/MapSelected";
|
||||
import DIRECTORY_ID from "@/constants/directory-id";
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
import { apiMapsCreate } from "@/service/api-client/api-maps";
|
||||
import { uploadFileService } from "@/service/upload-service";
|
||||
import pickFile, { IFileData } from "@/utils/pickFile";
|
||||
import { router, useLocalSearchParams } from "expo-router";
|
||||
import { useState } from "react";
|
||||
import { LatLng } from "react-native-maps";
|
||||
import Toast from "react-native-toast-message";
|
||||
|
||||
export default function MapsCreate() {
|
||||
const { user } = useAuth();
|
||||
const { id } = useLocalSearchParams();
|
||||
const [selectedLocation, setSelectedLocation] = useState<LatLng | null>(null);
|
||||
const [name, setName] = useState<string>("");
|
||||
const [image, setImage] = useState<IFileData | null>(null);
|
||||
const [isLoading, setLoading] = useState(false);
|
||||
|
||||
const handleSubmit = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
let newData: any;
|
||||
newData = {
|
||||
authorId: user?.id,
|
||||
portofolioId: id,
|
||||
namePin: name,
|
||||
latitude: selectedLocation?.latitude,
|
||||
longitude: selectedLocation?.longitude,
|
||||
};
|
||||
|
||||
if (image) {
|
||||
const responseUpload = await uploadFileService({
|
||||
dirId: DIRECTORY_ID.map_image,
|
||||
imageUri: image?.uri,
|
||||
});
|
||||
|
||||
if (!responseUpload?.data?.id) {
|
||||
Toast.show({
|
||||
type: "error",
|
||||
text1: "Gagal mengunggah gambar",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const imageId = responseUpload?.data?.id;
|
||||
|
||||
newData = {
|
||||
authorId: user?.id,
|
||||
portofolioId: id,
|
||||
namePin: name,
|
||||
latitude: selectedLocation?.latitude,
|
||||
longitude: selectedLocation?.longitude,
|
||||
imageId: imageId,
|
||||
};
|
||||
}
|
||||
|
||||
const response = await apiMapsCreate({
|
||||
data: newData,
|
||||
});
|
||||
|
||||
if (!response.success) {
|
||||
Toast.show({
|
||||
type: "error",
|
||||
text1: "Gagal menambahkan map",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
Toast.show({
|
||||
type: "success",
|
||||
text1: "Map berhasil ditambahkan",
|
||||
});
|
||||
router.back();
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const buttonFooter = (
|
||||
<BoxButtonOnFooter>
|
||||
<ButtonCustom
|
||||
onPress={() => {
|
||||
console.log(`Simpan maps ${id}`);
|
||||
router.replace(`/portofolio/${id}`);
|
||||
}}
|
||||
isLoading={isLoading}
|
||||
disabled={!selectedLocation || name === ""}
|
||||
onPress={handleSubmit}
|
||||
>
|
||||
Simpan
|
||||
</ButtonCustom>
|
||||
@@ -30,25 +105,34 @@ export default function MapsCreate() {
|
||||
<ViewWrapper footerComponent={buttonFooter}>
|
||||
<InformationBox text="Tentukan lokasi pin map dengan menekan pada map." />
|
||||
|
||||
<BaseBox style={{ height: 400 }}>
|
||||
<TextCustom>Maps Her</TextCustom>
|
||||
<BaseBox>
|
||||
<MapSelected
|
||||
selectedLocation={selectedLocation as any}
|
||||
setSelectedLocation={setSelectedLocation}
|
||||
/>
|
||||
</BaseBox>
|
||||
|
||||
<TextInputCustom
|
||||
required
|
||||
label="Nama Pin"
|
||||
placeholder="Masukkan nama pin maps"
|
||||
value={name}
|
||||
onChangeText={setName}
|
||||
/>
|
||||
|
||||
<Spacing height={50} />
|
||||
|
||||
<InformationBox text="Upload foto lokasi bisnis anda untuk ditampilkan dalam detail maps." />
|
||||
<LandscapeFrameUploaded />
|
||||
<LandscapeFrameUploaded image={image?.uri} />
|
||||
<ButtonCenteredOnly
|
||||
icon="upload"
|
||||
onPress={() => {
|
||||
console.log("Upload foto ");
|
||||
router.navigate(`/take-picture/${id}`);
|
||||
pickFile({
|
||||
allowedType: "image",
|
||||
setImageUri(file) {
|
||||
setImage(file);
|
||||
},
|
||||
});
|
||||
}}
|
||||
>
|
||||
Upload
|
||||
|
||||
@@ -1,19 +1,234 @@
|
||||
import { MapCustom, ViewWrapper } from "@/components";
|
||||
import {
|
||||
ButtonCustom,
|
||||
DrawerCustom,
|
||||
DummyLandscapeImage,
|
||||
Grid,
|
||||
Spacing,
|
||||
StackCustom,
|
||||
TextCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import GridTwoView from "@/components/_ShareComponent/GridTwoView";
|
||||
import API_IMAGE from "@/constants/api-storage";
|
||||
import { ICON_SIZE_SMALL } from "@/constants/constans-value";
|
||||
import { apiMapsGetAll } from "@/service/api-client/api-maps";
|
||||
import { openInDeviceMaps } from "@/utils/openInDeviceMaps";
|
||||
import { FontAwesome, Ionicons } from "@expo/vector-icons";
|
||||
import { Image } from "expo-image";
|
||||
import { router, useFocusEffect } from "expo-router";
|
||||
import { useCallback, useState } from "react";
|
||||
import { View } from "react-native";
|
||||
import MapView from "react-native-maps";
|
||||
import MapView, { Marker } from "react-native-maps";
|
||||
|
||||
const defaultRegion = {
|
||||
latitude: -8.737109,
|
||||
longitude: 115.1756897,
|
||||
latitudeDelta: 0.1,
|
||||
longitudeDelta: 0.1,
|
||||
height: 300,
|
||||
};
|
||||
|
||||
export interface LocationItem {
|
||||
id: string | number;
|
||||
latitude: number;
|
||||
longitude: number;
|
||||
name: string;
|
||||
imageId?: string;
|
||||
}
|
||||
|
||||
export default function Maps() {
|
||||
const [list, setList] = useState<any[] | null>(null);
|
||||
const [loadList, setLoadList] = useState(false);
|
||||
const [openDrawer, setOpenDrawer] = useState(false);
|
||||
const [selected, setSelected] = useState({
|
||||
id: "",
|
||||
bidangBisnis: "",
|
||||
nomorTelepon: "",
|
||||
alamatBisnis: "",
|
||||
namePin: "",
|
||||
imageId: "",
|
||||
portofolioId: "",
|
||||
latitude: 0,
|
||||
longitude: 0,
|
||||
});
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
handlerLoadList();
|
||||
}, [])
|
||||
);
|
||||
|
||||
const handlerLoadList = async () => {
|
||||
try {
|
||||
setLoadList(true);
|
||||
const response = await apiMapsGetAll();
|
||||
|
||||
if (response.success) {
|
||||
setList(response.data);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
} finally {
|
||||
setLoadList(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<ViewWrapper style={{ paddingInline: 0, paddingBlock: 0 }}>
|
||||
{/* <MapCustom height={"100%"} /> */}
|
||||
<View style={{ flex: 1 }}>
|
||||
<MapView
|
||||
style={{
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
</ViewWrapper>
|
||||
<>
|
||||
<ViewWrapper style={{ paddingInline: 0, paddingBlock: 0 }}>
|
||||
{/* <MapCustom height={"100%"} /> */}
|
||||
<View style={{ flex: 1 }}>
|
||||
{loadList ? (
|
||||
<MapView
|
||||
initialRegion={defaultRegion}
|
||||
style={{
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<MapView
|
||||
initialRegion={defaultRegion}
|
||||
style={{
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
}}
|
||||
>
|
||||
{list?.map((item: any, index: number) => {
|
||||
return (
|
||||
<Marker
|
||||
key={item?.id}
|
||||
coordinate={{
|
||||
latitude: item?.latitude,
|
||||
longitude: item?.longitude,
|
||||
}}
|
||||
title={item?.namePin}
|
||||
onPress={() => {
|
||||
setOpenDrawer(true);
|
||||
setSelected({
|
||||
id: item?.id,
|
||||
bidangBisnis:
|
||||
item?.Portofolio?.MasterBidangBisnis?.name,
|
||||
nomorTelepon: item?.Portofolio?.tlpn,
|
||||
alamatBisnis: item?.Portofolio?.alamatKantor,
|
||||
namePin: item?.namePin,
|
||||
imageId: item?.imageId,
|
||||
portofolioId: item?.Portofolio?.id,
|
||||
latitude: item?.latitude,
|
||||
longitude: item?.longitude,
|
||||
});
|
||||
}}
|
||||
// Gunakan gambar kustom jika tersedia
|
||||
>
|
||||
<View style={{}}>
|
||||
<Image
|
||||
source={{
|
||||
uri: API_IMAGE.GET({
|
||||
fileId: item?.Portofolio?.logoId,
|
||||
}),
|
||||
}}
|
||||
style={{
|
||||
width: 30,
|
||||
height: 30,
|
||||
borderRadius: 100,
|
||||
borderWidth: 1,
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
</Marker>
|
||||
);
|
||||
})}
|
||||
</MapView>
|
||||
)}
|
||||
</View>
|
||||
</ViewWrapper>
|
||||
|
||||
<DrawerCustom
|
||||
isVisible={openDrawer}
|
||||
closeDrawer={() => setOpenDrawer(false)}
|
||||
height={"auto"}
|
||||
>
|
||||
<DummyLandscapeImage height={200} imageId={selected.imageId} />
|
||||
<Spacing />
|
||||
<StackCustom gap={"xs"}>
|
||||
<GridTwoView
|
||||
spanLeft={2}
|
||||
spanRight={10}
|
||||
leftIcon={
|
||||
<FontAwesome
|
||||
name="building-o"
|
||||
size={ICON_SIZE_SMALL}
|
||||
color="white"
|
||||
/>
|
||||
}
|
||||
rightIcon={<TextCustom>{selected.namePin}</TextCustom>}
|
||||
/>
|
||||
|
||||
<GridTwoView
|
||||
spanLeft={2}
|
||||
spanRight={10}
|
||||
leftIcon={
|
||||
<Ionicons
|
||||
name="list-outline"
|
||||
size={ICON_SIZE_SMALL}
|
||||
color="white"
|
||||
/>
|
||||
}
|
||||
rightIcon={<TextCustom>{selected.bidangBisnis}</TextCustom>}
|
||||
/>
|
||||
|
||||
<GridTwoView
|
||||
spanLeft={2}
|
||||
spanRight={10}
|
||||
leftIcon={
|
||||
<Ionicons
|
||||
name="call-outline"
|
||||
size={ICON_SIZE_SMALL}
|
||||
color="white"
|
||||
/>
|
||||
}
|
||||
rightIcon={<TextCustom>{selected.nomorTelepon}</TextCustom>}
|
||||
/>
|
||||
<GridTwoView
|
||||
spanLeft={2}
|
||||
spanRight={10}
|
||||
leftIcon={
|
||||
<Ionicons
|
||||
name="location-outline"
|
||||
size={ICON_SIZE_SMALL}
|
||||
color="white"
|
||||
/>
|
||||
}
|
||||
rightIcon={<TextCustom>{selected.alamatBisnis}</TextCustom>}
|
||||
/>
|
||||
|
||||
<Grid>
|
||||
<Grid.Col span={6} style={{ paddingRight: 10 }}>
|
||||
<ButtonCustom
|
||||
onPress={() => {
|
||||
setOpenDrawer(false);
|
||||
router.push(`/portofolio/${selected.portofolioId}`);
|
||||
}}
|
||||
>
|
||||
Detail
|
||||
</ButtonCustom>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={6} style={{ paddingLeft: 10 }}>
|
||||
<ButtonCustom
|
||||
onPress={() => {
|
||||
openInDeviceMaps({
|
||||
latitude: selected.latitude,
|
||||
longitude: selected.longitude,
|
||||
title: selected.namePin,
|
||||
});
|
||||
}}
|
||||
>
|
||||
Buka Maps
|
||||
</ButtonCustom>
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
</StackCustom>
|
||||
</DrawerCustom>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||