Compare commits

...

37 Commits

Author SHA1 Message Date
5d4328a139 Integrasi API: Donation Admin
Add:
-  screens/Admin/Donation/funDonationUpdateStatus.ts
-  utils/countDownAndCondition.ts

Fix:
- app/(application)/(user)/donation/[id]/index.tsx
- app/(application)/admin/donation/[id]/[status]/index.tsx
- app/(application)/admin/donation/[id]/list-of-donatur.tsx
- app/(application)/admin/donation/[id]/reject-input.tsx
- app/(application)/admin/donation/index.tsx
- app/(application)/admin/event/[id]/[status]/index.tsx
- app/(application)/admin/voting/[id]/[status]/index.tsx
- screens/Admin/Donation/BoxOfDonationStory.tsx
- screens/Donation/BoxPublish.tsx
- screens/Donation/ComponentBoxDetailData.tsx
- service/api-admin/api-admin-donation.ts
- service/api-client/api-master.ts
- utils/colorBadge.ts
git add . && git commit -m
2025-10-28 10:19:47 +08:00
125bf16605 Update new github 2025-10-27 10:51:57 +08:00
73a803f2e8 Update new github 2025-10-27 10:49:31 +08:00
1bcd1a044f Integrasi API: Event Type
Add:
- utils/colorActivationForBadge.ts

Fix:
- app/(application)/admin/event/type-create.tsx
- app/(application)/admin/event/type-of-event.tsx
- app/(application)/admin/event/type-update.tsx
- service/api-admin/api-admin-event.ts
- service/api-admin/api-master-admin.ts
- service/api-client/api-event.ts

### No Issue
2025-10-24 16:22:45 +08:00
1e0b72de22 Integrasi API: Event Qr Code
Fix:
- app/(application)/(user)/event/[id]/confirmation.tsx
- app/(application)/(user)/event/[id]/list-of-participants.tsx
- app/(application)/admin/event/[id]/[status]/index.tsx
- app/(application)/admin/event/[id]/list-of-participants.tsx
- components/DateInput/DataTimeAndroid.tsx
- components/DateInput/DateTimeIOS.tsx
- service/api-admin/api-admin-event.ts

### No Issue
2025-10-24 11:57:05 +08:00
36dbfa3296 Integrasi API: Admin Event & Event user ui
Fix:
Metode konfirmasi pada event menggunakan QR Code
Perbaikan file pada:
- app/(application)/(user)/event/(tabs)/index.tsx
- app/(application)/(user)/event/[id]/confirmation.tsx
- app/(application)/(user)/event/[id]/publish.tsx
- app/(application)/admin/event/[id]/[status]/index.tsx
- app/(application)/admin/event/[id]/reject-input.tsx
- app/(application)/admin/event/[status]/status.tsx
- service/api-admin/api-admin-event.ts
- service/api-client/api-event.ts

### Issue: Belum menyertakan fungsi konfrimasu kehadiran
2025-10-23 17:47:36 +08:00
966e55597c Integrasi API: Admin event & QR Code event
Add:
- app/(application)/(user)/event/[id]/confirmation.tsx
- screens/Admin/Event/
- service/api-admin/api-admin-event.ts

Fix:
- app.config.js : penambahan DEEP_LINK_URL untuk url QR Code
- app/(application)/(user)/event/create.tsx
- app/(application)/admin/event/[id]/[status]/index.tsx
- app/(application)/admin/event/[id]/reject-input.tsx
- app/(application)/admin/event/[status]/status.tsx
- app/(application)/admin/event/index.tsx
- app/(application)/admin/job/[id]/[status]/index.tsx
- screens/Admin/listPageAdmin.tsx
- service/api-config.ts
-

### No Issue
2025-10-22 15:45:58 +08:00
4da55a5a8a Integrasi API: Voting admin
Add:
- app/(application)/admin/voting/[id]/[status]/reject-input.tsx
- app/(application)/admin/voting/history.tsx
- components/Box/ReportBox.tsx
- screens/Admin/Voting/
- utils/colorBadge.ts

Fix:
- app/(application)/(user)/job/[id]/[status]/detail.tsx
- app/(application)/(user)/voting/[id]/[status]/detail.tsx
- app/(application)/admin/job/[id]/[status]/index.tsx
- app/(application)/admin/job/[id]/[status]/reject-input.tsx
- app/(application)/admin/voting/[id]/[status]/index.tsx
- app/(application)/admin/voting/[id]/reject-input.tsx
- app/(application)/admin/voting/[status]/status.tsx
- components/Container/CircleContainer.tsx
- components/Text/TextCustom.tsx
- components/_ShareComponent/Admin/ButtonReview.tsx
- screens/Admin/Job/funUpdateStatus.ts
- screens/Admin/listPageAdmin.tsx
- service/api-admin/api-admin-voting.ts

### No Issue
2025-10-21 16:52:17 +08:00
faf0f36e53 UI Fix: Pada tampilan ios bagian button ada yang ta terlihat dan sudah di perbaiki
### No Issue
2025-10-21 11:17:37 +08:00
57285e5697 Integrasi API: Admin Voting
Add:
-  service/api-admin/api-admin-voting.ts

Fix:
- app/(application)/admin/voting/[id]/[status]/index.tsx
- app/(application)/admin/voting/[status]/status.tsx
- app/(application)/admin/voting/index.tsx

### No Issue
2025-10-20 17:39:12 +08:00
1fd9694ebf Integrasi API: Admin Forum
Fix:
app/(application)/admin/forum/[id]/index.tsx
app/(application)/admin/forum/[id]/list-comment.tsx
app/(application)/admin/forum/[id]/list-report-comment.tsx
app/(application)/admin/forum/[id]/list-report-posting.tsx
app/(application)/admin/forum/index.tsx
app/(application)/admin/forum/posting.tsx
app/(application)/admin/forum/report-comment.tsx
app/(application)/admin/forum/report-posting.tsx
screens/Admin/listPageAdmin.tsx
service/api-admin/api-admin-forum.ts

### No Issue
2025-10-20 16:34:38 +08:00
d31df8c390 Integrasi API: Admin forum
Add:
app/(application)/admin/forum/[id]/list-comment.tsx
        components/_Icon/IconOpenTo.tsx
        service/api-admin/api-admin-forum.ts

Fix:
app/(application)/admin/collaboration/index.tsx
app/(application)/admin/forum/[id]/index.tsx
app/(application)/admin/forum/[id]/list-report-comment.tsx
app/(application)/admin/forum/index.tsx
app/(application)/admin/forum/posting.tsx

### Issue: Report komentar masih belum berfungsi
2025-10-17 17:41:25 +08:00
90cfb042d8 Integrasi API: Admin Job
Add:
app/(application)/admin/job/[id]/[status]/reject-input.tsx
screens/Admin/Job/

Fix:
Tampilan ke UI sudah terintegrasi

### No Issue
2025-10-17 14:15:21 +08:00
b9f93ff46a Menjalankan perintah: eas build --profile preview
### Issue: Harus ada SDK nya jika local
2025-10-17 10:24:04 +08:00
0770237fe5 Integrasi API: Admin Job
Add:
-  service/api-admin/api-admin-job.ts

Fix:
 modified:   app/(application)/admin/job/[status]/status.tsx
        modified:   app/(application)/admin/job/index.tsx
        modified:   components/_ShareComponent/SearchInput.tsx

### No issue
2025-10-16 16:44:43 +08:00
6f4dd79568 Integrasi API: Admin Collaboration
Fix: Tampilan yang terintegrasi API
 - app/(application)/(user)/collaboration/[id]/edit.tsx
 - app/(application)/admin/collaboration/[id]/[status].tsx
 - app/(application)/admin/collaboration/[id]/group.tsx
 - app/(application)/admin/collaboration/[id]/reject-input.tsx
 - app/(application)/admin/collaboration/group.tsx
 - app/(application)/admin/collaboration/index.tsx
 - app/(application)/admin/collaboration/publish.tsx
 - app/(application)/admin/collaboration/reject.tsx
 - components/_ShareComponent/Admin/TableValue.tsx
 - screens/Collaboration/BoxPublishSection.tsx
 - service/api-admin/api-admin-collaboration.ts

### No Issue
2025-10-16 14:48:31 +08:00
9faa0b0f64 Collaboration Mobile API
Add:
- /admin/collaboration/[id]/reject-input.tsx
- /api-admin/api-admin-collaboration.ts

Fix:
- Integrasi ke tampilan UI
app/(application)/admin/collaboration/[id]/[status].tsx
app/(application)/admin/collaboration/index.tsx
app/(application)/admin/collaboration/publish.tsx
components/_ShareComponent/Admin/TableTitle.tsx
components/_ShareComponent/Admin/TableValue.tsx

### No Issue
2025-10-15 17:30:25 +08:00
e05a7c8701 Portofolio
Fix:
(application)/(user)/portofolio/[id]/index.
Button hapus hanya muncul jika user login adalah author

### No Issue
2025-10-15 15:45:01 +08:00
f50c5099d8 Integrasi API: App Information & Maps
Add:
- service/api-admin/api-master-admin.ts

Fix:
app/(application)/admin/app-information/business-field/[id]/index.tsx
app/(application)/admin/app-information/business-field/create.tsx
app/(application)/admin/app-information/index.tsx
app/(application)/admin/app-information/information-bank/[id]/index.tsx
app/(application)/admin/app-information/information-bank/create.tsx
app/(application)/admin/maps.tsx
screens/Admin/App-Information/BusinessFieldSection.tsx
screens/Admin/App-Information/InformationBankSection.tsx
screens/Admin/App-Information/StickerSection.tsx
screens/Authentication/LoginView.tsx
service/api-client/api-master.ts

- Perbaikan berupa integrasi API

### No Issue
2025-10-15 15:09:18 +08:00
5f36620988 Integrasi Admin: User Acces & Super Admin
Add:
- admin/super-admin/
- admin/user-access/
- service/api-admin/

Fix:
- (user)/profile/[id]/index: penambahan useData dari useAuthuntuk merestart value masterRole
- integrasi pada tampilan admin

### No Issue
2025-10-14 17:28:40 +08:00
f750d158be Integrasi Map Business
Add:
 components/Map/MapSelected.tsx
        components/_ShareComponent/GridTwoView.tsx
        service/api-client/api-maps.ts
utils/openInDeviceMaps.ts

Fix:
 modified:   app/(application)/(user)/maps/[id]/edit.tsx
        modified:   app/(application)/(user)/maps/create.tsx
        modified:   app/(application)/(user)/maps/index.tsx
        modified:   app/(application)/(user)/portofolio/[id]/index.tsx
        modified:   components/Map/MapCustom.tsx
        modified:   screens/Portofolio/BusinessLocationSection.tsx
        modified:   screens/Portofolio/DataPortofolio.tsx
        modified:   screens/Portofolio/ListPage.tsx

### No issue
2025-10-13 17:46:47 +08:00
0e7b29bb15 Integrasi API Donation
Fix:
- (application)/(user)/donation/[id]/(news)/[news]/edit-news
- (application)/(user)/donation/[id]/(news)/[news]/index
- (application)/(user)/donation/[id]/(news)/add-news
- (application)/(user)/donation/[id]/(news)/list-of-news
- (application)/(user)/donation/[id]/(news)/recap-of-news
- (application)/(user)/donation/[id]/infromation-fundrising
- service/api-client/api-donation

### No Issue
2025-10-09 17:00:04 +08:00
b293310969 Donation:
Add:
- components/_ShareComponent/MoneyTransferAnimation.tsx

Fix:
- Invoice terintegrasi API
- Create dan list berita

### No Issue
2025-10-08 17:40:36 +08:00
a980397640 Donation
Add:
- (user)/donation/[id]/(transaction-flow)/[invoiceId]

Fix:
Integrasi dalam alur transaksi
- Buat invoice dan list transaksi

### No Issue
2025-10-07 17:40:54 +08:00
7c82e8b588 Donation:
Fix:
- Edit story & bank account
- List beranda dan detailnya

- Tampilan penggalang dana

### No Issue
2025-10-07 15:52:51 +08:00
53cdca21fc Donasi
Fix:
- service/api-client/api-donation.ts: penambahan fetch update, hapus dan update status donasi
- /(application)/(user)/donation/[id]/edit.tsx: integrasi ke API

### No issue
2025-10-06 17:32:18 +08:00
ba878d4d08 Donasi
Fix: tampilan status dan detail status sudah terintegrasi API
- create dan buntton status sudah terintegrasi

### No Issue
2025-10-06 16:11:56 +08:00
f3a3acc747 Donation:
Add:
- service/api-client/api-donation.ts

Fix:
- service/api-client/api-master.ts : tambah master donasi
- app/(application)/(user)/donation/create.tsx
- app/(application)/(user)/donation/create-story.tsx

### No issue
2025-10-03 17:37:29 +08:00
a6389174d7 git add . && git commit -m 2025-10-03 14:09:31 +08:00
2be4afdcb1 Investment
Add:
-  components/Button/CoyButton.tsx
-  constants/local-storage-key.ts

Fix:
- Integrasi pada proses transaksi pmebelian investasi

### No Issue
2025-10-02 17:29:25 +08:00
aa85e05f79 Invesment
Fix: Integrasi API ke UI
- investment/[id]/(transaction-flow)/index.tsx
- investment/[id]/index.tsx

### Issue: input data untuk total lembar dan sisa lembar berisi . dianatra angkanya
2025-10-01 17:29:59 +08:00
c2acb97a37 Invesment
Fix:
- tampilan list data bada beranda dan detail data main

### No Issue
2025-10-01 16:45:23 +08:00
250b216a54 Invesment
Fix:
- tampilan dokumen dan file prospektus
- create & edit dokumen
- list rekap dokumen dan tampilan ( untuk non author )

### No Issue
2025-10-01 14:40:50 +08:00
5f05d1f7f0 Component
Fix: fix nama upload dan delete file service

Invesment:
Fix:
- Edit data dan edit prospektus
- View file dengan metode react-native-webview

### No issue
2025-09-30 17:30:07 +08:00
3d8d8568a3 Invesment
Fix: create & edit list master sudah terintegrasi ke API

### No Issue
2025-09-30 11:00:31 +08:00
ccdd7730b2 Investment
Add:
- utils/pickFile: pilih extention file sesuai kebutuhan
- utils/formatCurrencyDisplay.ts: tampillan uang 2.500
- api-client/api-investment.ts
- api-storage.ts: api strogre wibudev

Fix:
- Integrasi API pada: Create, Edit, Tampilan status & detail
- Button status dan hapus data juga sudah terintegrasi

### No Issue
2025-09-29 17:42:25 +08:00
a474aebb94 Forum:
Fix: link dari avatar ke forumku & penambahan tombol create di forumku jika id forum milik user yang sama seperti user login

### No issue
2025-09-29 10:28:53 +08:00
220 changed files with 12494 additions and 6160 deletions

42
.gitignore vendored
View File

@@ -40,3 +40,45 @@ app-example
.qodo .qodo
.env .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

View File

@@ -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) * The preferred build flavor of JavaScriptCore (JSC)
@@ -94,6 +94,8 @@ android {
targetSdkVersion rootProject.ext.targetSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 1 versionCode 1
versionName "1.0.0" versionName "1.0.0"
buildConfigField "String", "REACT_NATIVE_RELEASE_LEVEL", "\"${findProperty('reactNativeReleaseLevel') ?: 'stable'}\""
} }
signingConfigs { signingConfigs {
debug { debug {
@@ -111,15 +113,18 @@ android {
// Caution! In production, you need to generate your own keystore file. // Caution! In production, you need to generate your own keystore file.
// see https://reactnative.dev/docs/signed-apk-android. // see https://reactnative.dev/docs/signed-apk-android.
signingConfig signingConfigs.debug signingConfig signingConfigs.debug
shrinkResources (findProperty('android.enableShrinkResourcesInReleaseBuilds')?.toBoolean() ?: false) def enableShrinkResources = findProperty('android.enableShrinkResourcesInReleaseBuilds') ?: 'false'
minifyEnabled enableProguardInReleaseBuilds shrinkResources enableShrinkResources.toBoolean()
minifyEnabled enableMinifyInReleaseBuilds
proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro" proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
crunchPngs (findProperty('android.enablePngCrunchInReleaseBuilds')?.toBoolean() ?: true) def enablePngCrunchInRelease = findProperty('android.enablePngCrunchInReleaseBuilds') ?: 'true'
crunchPngs enablePngCrunchInRelease.toBoolean()
} }
} }
packagingOptions { packagingOptions {
jniLibs { jniLibs {
useLegacyPackaging (findProperty('expo.useLegacyPackaging')?.toBoolean() ?: false) def enableLegacyPackaging = findProperty('expo.useLegacyPackaging') ?: 'false'
useLegacyPackaging enableLegacyPackaging.toBoolean()
} }
} }
androidResources { androidResources {

View File

@@ -13,7 +13,7 @@
<data android:scheme="https"/> <data android:scheme="https"/>
</intent> </intent>
</queries> </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.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_CHECK_ON_LAUNCH" android:value="ALWAYS"/>
<meta-data android:name="expo.modules.updates.EXPO_UPDATES_LAUNCH_WAIT_MS" android:value="0"/> <meta-data android:name="expo.modules.updates.EXPO_UPDATES_LAUNCH_WAIT_MS" android:value="0"/>

View File

@@ -5,13 +5,13 @@ import android.content.res.Configuration
import com.facebook.react.PackageList import com.facebook.react.PackageList
import com.facebook.react.ReactApplication import com.facebook.react.ReactApplication
import com.facebook.react.ReactNativeApplicationEntryPoint.loadReactNative
import com.facebook.react.ReactNativeHost import com.facebook.react.ReactNativeHost
import com.facebook.react.ReactPackage import com.facebook.react.ReactPackage
import com.facebook.react.ReactHost 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.defaults.DefaultReactNativeHost
import com.facebook.react.soloader.OpenSourceMergedSoMapping
import com.facebook.soloader.SoLoader
import expo.modules.ApplicationLifecycleDispatcher import expo.modules.ApplicationLifecycleDispatcher
import expo.modules.ReactNativeHostWrapper import expo.modules.ReactNativeHostWrapper
@@ -19,21 +19,19 @@ import expo.modules.ReactNativeHostWrapper
class MainApplication : Application(), ReactApplication { class MainApplication : Application(), ReactApplication {
override val reactNativeHost: ReactNativeHost = ReactNativeHostWrapper( override val reactNativeHost: ReactNativeHost = ReactNativeHostWrapper(
this, this,
object : DefaultReactNativeHost(this) { object : DefaultReactNativeHost(this) {
override fun getPackages(): List<ReactPackage> { override fun getPackages(): List<ReactPackage> =
val packages = PackageList(this).packages PackageList(this).packages.apply {
// Packages that cannot be autolinked yet can be added manually here, for example: // Packages that cannot be autolinked yet can be added manually here, for example:
// packages.add(MyReactNativePackage()) // add(MyReactNativePackage())
return packages }
}
override fun getJSMainModuleName(): String = ".expo/.virtual-metro-entry" override fun getJSMainModuleName(): String = ".expo/.virtual-metro-entry"
override fun getUseDeveloperSupport(): Boolean = BuildConfig.DEBUG override fun getUseDeveloperSupport(): Boolean = BuildConfig.DEBUG
override val isNewArchEnabled: Boolean = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED 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() { override fun onCreate() {
super.onCreate() super.onCreate()
SoLoader.init(this, OpenSourceMergedSoMapping) DefaultNewArchitectureEntryPoint.releaseLevel = try {
if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) { ReleaseLevel.valueOf(BuildConfig.REACT_NATIVE_RELEASE_LEVEL.uppercase())
// If you opted-in for the New Architecture, we load the native entry point for this app. } catch (e: IllegalArgumentException) {
load() ReleaseLevel.STABLE
} }
loadReactNative(this)
ApplicationLifecycleDispatcher.onApplicationCreate(this) ApplicationLifecycleDispatcher.onApplicationCreate(this)
} }

View File

@@ -1,5 +1,6 @@
<resources xmlns:tools="http://schemas.android.com/tools"> <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="android:editTextBackground">@drawable/rn_edit_text_material</item>
<item name="colorPrimary">@color/colorPrimary</item> <item name="colorPrimary">@color/colorPrimary</item>
<item name="android:statusBarColor">#ffffff</item> <item name="android:statusBarColor">#ffffff</item>
@@ -8,5 +9,6 @@
<item name="windowSplashScreenBackground">@color/splashscreen_background</item> <item name="windowSplashScreenBackground">@color/splashscreen_background</item>
<item name="windowSplashScreenAnimatedIcon">@drawable/splashscreen_logo</item> <item name="windowSplashScreenAnimatedIcon">@drawable/splashscreen_logo</item>
<item name="postSplashScreenTheme">@style/AppTheme</item> <item name="postSplashScreenTheme">@style/AppTheme</item>
<item name="android:windowSplashScreenBehavior">icon_preferred</item>
</style> </style>
</resources> </resources>

View File

@@ -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 { allprojects {
repositories { repositories {
maven {
// All of React Native (JS, Obj-C sources, Android binaries) is installed from npm
url(reactNativeAndroidDir)
}
google() google()
mavenCentral() mavenCentral()
maven { url 'https://www.jitpack.io' } maven { url 'https://www.jitpack.io' }

View File

@@ -15,7 +15,7 @@ org.gradle.jvmargs=-Xmx2048m -XX:MaxMetaspaceSize=512m
# When configured, Gradle will run in incubating parallel mode. # When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit # 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 # 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 # 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 # 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. # If set to false, you will be using JSC instead.
hermesEnabled=true 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) # Enable GIF support in React Native images (~200 B increase)
expo.gif.enabled=true expo.gif.enabled=true
# Enable webp support in React Native images (~85 KB increase) # 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. # Use legacy packaging to compress native libraries in the resulting APK.
expo.useLegacyPackaging=false expo.useLegacyPackaging=false
# Whether the app is configured to use edge-to-edge via the app config or `react-native-edge-to-edge` plugin # Specifies whether the app is configured to use edge-to-edge via the app config or plugin
expo.edgeToEdgeEnabled=true # 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

Binary file not shown.

View File

@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists 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 networkTimeout=10000
validateDistributionUrl=true validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME

4
android/gradlew vendored
View File

@@ -114,7 +114,7 @@ case "$( uname )" in #(
NONSTOP* ) nonstop=true ;; NONSTOP* ) nonstop=true ;;
esac esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar CLASSPATH="\\\"\\\""
# Determine the Java command to use to start the JVM. # Determine the Java command to use to start the JVM.
@@ -213,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
set -- \ set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \ "-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \ -classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \ -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
"$@" "$@"
# Stop when "xargs" is not available. # Stop when "xargs" is not available.

4
android/gradlew.bat vendored
View File

@@ -70,11 +70,11 @@ goto fail
:execute :execute
@rem Setup the command line @rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar set CLASSPATH=
@rem Execute Gradle @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 :end
@rem End local scope for the variables with windows NT shell @rem End local scope for the variables with windows NT shell

View File

@@ -70,5 +70,6 @@ export default {
// Tambahkan environment variables ke sini // Tambahkan environment variables ke sini
API_BASE_URL: process.env.API_BASE_URL, API_BASE_URL: process.env.API_BASE_URL,
BASE_URL: process.env.BASE_URL, BASE_URL: process.env.BASE_URL,
DEEP_LINK_URL: process.env.DEEP_LINK_URL,
}, },
}; };

View File

@@ -1,9 +1,13 @@
import { BackButton, ViewWrapper } from "@/components"; import { BackButton } from "@/components";
import { MainColor } from "@/constants/color-palet"; import PdfViewer from "@/components/_ShareComponent/PdfViewer";
import { FontAwesome } from "@expo/vector-icons"; import API_STRORAGE from "@/constants/base-url-api-strorage";
import { Stack } from "expo-router"; import { Stack, useLocalSearchParams } from "expo-router";
import { SafeAreaView } from "react-native-safe-area-context";
export default function FileScreen() { export default function FileScreen() {
const { id } = useLocalSearchParams();
const url = API_STRORAGE.GET({ fileId: id as string });
return ( return (
<> <>
<Stack.Screen <Stack.Screen
@@ -12,14 +16,9 @@ export default function FileScreen() {
headerLeft: () => <BackButton />, headerLeft: () => <BackButton />,
}} }}
/> />
<ViewWrapper> <SafeAreaView style={{ flex: 1 }} edges={["bottom"]}>
<FontAwesome <PdfViewer uri={url} />
name="file-pdf-o" </SafeAreaView>
size={300}
style={{ alignSelf: "center" }}
color={MainColor.white}
/>
</ViewWrapper>
</> </>
); );
} }

View File

@@ -10,7 +10,6 @@ export default function UserLayout() {
return ( return (
<> <>
<Stack screenOptions={HeaderStyles}> <Stack screenOptions={HeaderStyles}>
<Stack.Screen <Stack.Screen
name="waiting-room" name="waiting-room"
options={{ options={{
@@ -186,7 +185,7 @@ export default function UserLayout() {
name="crowdfunding/index" name="crowdfunding/index"
options={{ options={{
title: "Crowdfunding", title: "Crowdfunding",
headerLeft: () => <BackButton />, headerLeft: () => <BackButton path="/home" />,
}} }}
/> />
@@ -449,7 +448,7 @@ export default function UserLayout() {
}} }}
/> />
<Stack.Screen <Stack.Screen
name="donation/[id]/(transaction-flow)/[transaction]/invoice" name="donation/[id]/(transaction-flow)/[invoiceId]/invoice"
options={{ options={{
title: "Invoice", title: "Invoice",
headerLeft: () => ( headerLeft: () => (
@@ -463,7 +462,7 @@ export default function UserLayout() {
}} }}
/> />
<Stack.Screen <Stack.Screen
name="donation/[id]/(transaction-flow)/[transaction]/process" name="donation/[id]/(transaction-flow)/[invoiceId]/process"
options={{ options={{
title: "Proses", title: "Proses",
headerLeft: () => ( headerLeft: () => (
@@ -477,14 +476,14 @@ export default function UserLayout() {
}} }}
/> />
<Stack.Screen <Stack.Screen
name="donation/[id]/(transaction-flow)/[transaction]/success" name="donation/[id]/(transaction-flow)/[invoiceId]/success"
options={{ options={{
title: "Donasi Berhasil", title: "Donasi Berhasil",
headerLeft: () => <BackButton />, headerLeft: () => <BackButton />,
}} }}
/> />
<Stack.Screen <Stack.Screen
name="donation/[id]/(transaction-flow)/[transaction]/failed" name="donation/[id]/(transaction-flow)/[invoiceId]/failed"
options={{ options={{
title: "Donasi Gagal", title: "Donasi Gagal",
headerLeft: () => <BackButton />, headerLeft: () => <BackButton />,

View File

@@ -19,7 +19,6 @@ import Toast from "react-native-toast-message";
export default function CollaborationEdit() { export default function CollaborationEdit() {
const { id } = useLocalSearchParams(); const { id } = useLocalSearchParams();
console.log("id :", id);
const [data, setData] = useState<any>(); const [data, setData] = useState<any>();
const [listMaster, setListMaster] = useState<any[]>([]); const [listMaster, setListMaster] = useState<any[]>([]);
const [loadingData, setLoadingData] = useState(false); const [loadingData, setLoadingData] = useState(false);

View File

@@ -1,11 +1,41 @@
import { import {
FloatingButton, FloatingButton,
ViewWrapper LoaderCustom,
TextCustom,
ViewWrapper,
} from "@/components"; } from "@/components";
import Donation_BoxPublish from "@/screens/Donation/BoxPublish"; 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() { 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 ( return (
<ViewWrapper <ViewWrapper
hideFooter hideFooter
@@ -13,9 +43,15 @@ export default function DonationBeranda() {
<FloatingButton onPress={() => router.push("/donation/create")} /> <FloatingButton onPress={() => router.push("/donation/create")} />
} }
> >
{Array.from({ length: 10 }).map((_, index) => ( {loadList ? (
<Donation_BoxPublish key={index} id={index.toString()}/> <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> </ViewWrapper>
); );
} }

View File

@@ -1,76 +1,142 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { import {
BadgeCustom, BadgeCustom,
BaseBox, BaseBox,
DummyLandscapeImage, DummyLandscapeImage,
Grid, Grid,
LoaderCustom,
StackCustom, StackCustom,
TextCustom, TextCustom,
ViewWrapper, ViewWrapper,
} from "@/components"; } from "@/components";
import { dummyMasterStatusTransaction } from "@/lib/dummy-data/_master/status-transaction"; import { useAuth } from "@/hooks/use-auth";
import { router } from "expo-router"; import { apiDonationGetAll } from "@/service/api-client/api-donation";
import { formatCurrencyDisplay } from "@/utils/formatCurrencyDisplay";
import { Href, router, useFocusEffect } from "expo-router";
import _ from "lodash";
import { useCallback, useState } from "react";
import { View } from "react-native"; import { View } from "react-native";
export default function DonationMyDonation() { export default function DonationMyDonation() {
const randomStatusData = Array.from({ length: 10 }, () => { const { user } = useAuth();
const randomIndex = Math.floor( const [list, setList] = useState<any[] | null>(null);
Math.random() * dummyMasterStatusTransaction.length const [loadList, setLoadList] = useState(false);
);
return dummyMasterStatusTransaction[randomIndex];
});
const handlePress = (value: string) => { useFocusEffect(
if (value === "menunggu") { useCallback(() => {
router.push(`/donation/${value}/(transaction-flow)/123/invoice`); onLoadData();
} else if (value === "proses") { }, [user?.id])
router.push(`/donation/${value}/(transaction-flow)/123/process`); );
} else if (value === "berhasil") {
router.push(`/donation/${value}/(transaction-flow)/123/success`); const onLoadData = async () => {
} else if (value === "gagal") { try {
router.push(`/donation/${value}/(transaction-flow)/123/failed`); 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 ( return (
<ViewWrapper hideFooter> <ViewWrapper hideFooter>
{randomStatusData.map((item, index) => ( {loadList ? (
<BaseBox <LoaderCustom />
key={index} ) : _.isEmpty(list) ? (
paddingTop={7} <TextCustom align="center" color="gray">
paddingBottom={7} Belum ada transaksi
onPress={() => { </TextCustom>
handlePress(item.value); ) : (
}} list?.map((item, index) => (
> <BaseBox
<Grid> key={index}
<Grid.Col span={5}> paddingTop={7}
<DummyLandscapeImage height={100} unClickPath /> paddingBottom={7}
</Grid.Col> onPress={() => {
<Grid.Col span={1}> handlePress({
<View /> status: _.lowerCase(item.statusInvoice),
</Grid.Col> invoiceId: item.id,
<Grid.Col span={6}> donationId: item.donasiId,
<StackCustom gap={"sm"}> });
<View> }}
<TextCustom truncate> >
Judul Donasi: Lorem ipsum dolor sit amet consectetur <Grid>
adipisicing elit. <Grid.Col span={5}>
<DummyLandscapeImage
height={100}
unClickPath
imageId={item.imageId}
/>
</Grid.Col>
<Grid.Col span={1}>
<View />
</Grid.Col>
<Grid.Col span={6}>
<StackCustom>
<TextCustom truncate={2} bold>
{item.title || "-"}
</TextCustom> </TextCustom>
</View>
<View>
<TextCustom>Donasi Saya</TextCustom>
<TextCustom bold color="yellow"> <TextCustom bold color="yellow">
Rp. 7.500.000 Rp. {formatCurrencyDisplay(item.nominal)}
</TextCustom> </TextCustom>
</View>
<BadgeCustom variant="light" color={item.color} fullWidth> <BadgeCustom
{item.label} variant="light"
</BadgeCustom> color={handlerColor(_.lowerCase(item.statusInvoice))}
</StackCustom> fullWidth
</Grid.Col> >
</Grid> {item.statusInvoice}
</BaseBox> </BadgeCustom>
))} </StackCustom>
</Grid.Col>
</Grid>
</BaseBox>
))
)}
</ViewWrapper> </ViewWrapper>
); );
} }

View File

@@ -1,12 +1,48 @@
import { ScrollableCustom, ViewWrapper } from "@/components"; /* eslint-disable react-hooks/exhaustive-deps */
import {
LoaderCustom,
ScrollableCustom,
TextCustom,
ViewWrapper,
} from "@/components";
import { useAuth } from "@/hooks/use-auth";
import { dummyMasterStatus } from "@/lib/dummy-data/_master/status"; import { dummyMasterStatus } from "@/lib/dummy-data/_master/status";
import Donasi_BoxStatus from "@/screens/Donation/BoxStatus"; 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() { export default function DonationStatus() {
const { user } = useAuth();
const [activeCategory, setActiveCategory] = useState<string | null>( const [activeCategory, setActiveCategory] = useState<string | null>(
"publish" "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) => { const handlePress = (item: any) => {
setActiveCategory(item.value); setActiveCategory(item.value);
@@ -26,13 +62,19 @@ export default function DonationStatus() {
); );
return ( return (
<ViewWrapper hideFooter headerComponent={scrollComponent}> <ViewWrapper hideFooter headerComponent={scrollComponent}>
{Array.from({ length: 10 }).map((_, index) => ( {loadList ? (
<Donasi_BoxStatus <LoaderCustom />
key={index} ) : _.isEmpty(listData) ? (
id={index.toString()} <TextCustom align="center">Tidak ada data {activeCategory}</TextCustom>
status={activeCategory as string} ) : (
/> listData?.map((item: any, index: number) => (
))} <Donasi_BoxStatus
key={index}
data={item}
status={activeCategory as string}
/>
))
)}
</ViewWrapper> </ViewWrapper>
); );
} }

View File

@@ -1,3 +1,4 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { import {
ButtonCenteredOnly, ButtonCenteredOnly,
ButtonCustom, ButtonCustom,
@@ -9,17 +10,120 @@ import {
TextInputCustom, TextInputCustom,
ViewWrapper, ViewWrapper,
} from "@/components"; } 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() { 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 ( return (
<ViewWrapper> <ViewWrapper>
<StackCustom gap={"xs"}> <StackCustom gap={"xs"}>
<InformationBox text="Upload gambar bersifat opsional untuk melengkapi kabar terkait donasi Anda." /> <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 <ButtonCenteredOnly
onPress={() => { onPress={() => {
router.push("/(application)/(image)/take-picture/123"); pickFile({
allowedType: "image",
setImageUri(file) {
setImage(file);
},
});
}} }}
icon="upload" icon="upload"
> >
@@ -30,6 +134,8 @@ export default function DonationEditNews() {
label="Judul Berita" label="Judul Berita"
placeholder="Masukan judul berita" placeholder="Masukan judul berita"
required required
value={data?.title}
onChangeText={(value) => setData({ ...data, title: value })}
/> />
<TextAreaCustom <TextAreaCustom
label="Deskripsi Berita" label="Deskripsi Berita"
@@ -37,12 +143,16 @@ export default function DonationEditNews() {
required required
showCount showCount
maxLength={1000} maxLength={1000}
value={data?.deskripsi}
onChangeText={(value) => setData({ ...data, deskripsi: value })}
/> />
<Spacing /> <Spacing />
<ButtonCustom <ButtonCustom
disabled={!data?.title || !data?.deskripsi}
isLoading={isLoading}
onPress={() => { onPress={() => {
router.back(); handlerSubmitUpdate();
}} }}
> >
Update Update

View File

@@ -1,3 +1,4 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { import {
AlertDefaultSystem, AlertDefaultSystem,
BackButton, BackButton,
@@ -12,13 +13,45 @@ import {
} from "@/components"; } from "@/components";
import { IconEdit } from "@/components/_Icon"; import { IconEdit } from "@/components/_Icon";
import { IconTrash } from "@/components/_Icon/IconTrash"; import { IconTrash } from "@/components/_Icon/IconTrash";
import dayjs from "dayjs"; import { useAuth } from "@/hooks/use-auth";
import { router, Stack, useLocalSearchParams } from "expo-router"; import {
import { useState } from "react"; 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() { export default function DonationNews() {
const { id, news } = useLocalSearchParams(); const { user } = useAuth();
const { news } = useLocalSearchParams();
const [openDrawer, setOpenDrawer] = useState(false); 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 ( return (
<> <>
@@ -26,28 +59,28 @@ export default function DonationNews() {
options={{ options={{
title: "Detail Kabar", title: "Detail Kabar",
headerLeft: () => <BackButton />, headerLeft: () => <BackButton />,
headerRight: () => <DotButton onPress={() => setOpenDrawer(true)} />, headerRight: () =>
user?.id === data?.authorId && (
<DotButton onPress={() => setOpenDrawer(true)} />
),
}} }}
/> />
<ViewWrapper> <ViewWrapper>
<BaseBox> <BaseBox>
<StackCustom> <StackCustom>
<TextCustom style={{ alignSelf: "flex-end" }}> <TextCustom style={{ alignSelf: "flex-end" }}>
{dayjs().format("DD MMM YYYY")} {formatChatTime(data?.createdAt)}
</TextCustom> </TextCustom>
<DummyLandscapeImage /> {data && data.imageId && (
<DummyLandscapeImage imageId={data.imageId} />
)}
<TextCustom bold size="large" align="center"> <TextCustom bold size="large" align="center">
Judul Berita {data?.title || "-"}
</TextCustom> </TextCustom>
<TextCustom> <TextCustom>{data?.deskripsi || "-"}</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>
</StackCustom> </StackCustom>
</BaseBox> </BaseBox>
</ViewWrapper> </ViewWrapper>
@@ -62,12 +95,12 @@ export default function DonationNews() {
{ {
icon: <IconEdit />, icon: <IconEdit />,
label: "Edit Berita", label: "Edit Berita",
path: `/donation/${id}/(news)/${news}/edit-news` as any, path: `/donation/[id]/(news)/${news}/edit-news` as any,
}, },
{ {
icon: <IconTrash />, icon: <IconTrash />,
label: "Hapus Berita", label: "Hapus Berita",
path: `` as any, path: "",
color: "red", color: "red",
}, },
]} ]}
@@ -79,7 +112,23 @@ export default function DonationNews() {
message: "Apakah Anda yakin ingin menghapus berita ini?", message: "Apakah Anda yakin ingin menghapus berita ini?",
textLeft: "Batal", textLeft: "Batal",
textRight: "Hapus", 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(); router.back();
}, },
}); });

View File

@@ -9,17 +9,79 @@ import {
TextInputCustom, TextInputCustom,
ViewWrapper, ViewWrapper,
} from "@/components"; } 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() { 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 ( return (
<ViewWrapper> <ViewWrapper>
<StackCustom gap={"xs"}> <StackCustom gap={"xs"}>
<InformationBox text="Upload gambar bersifat opsional untuk melengkapi kabar terkait donasi Anda." /> <InformationBox text="Upload gambar bersifat opsional untuk melengkapi kabar terkait donasi Anda." />
<LandscapeFrameUploaded /> <LandscapeFrameUploaded image={image?.uri} />
<ButtonCenteredOnly <ButtonCenteredOnly
onPress={() => { onPress={() => {
router.push("/(application)/(image)/take-picture/123"); pickFile({
allowedType: "image",
setImageUri(file) {
setImage(file);
},
});
}} }}
icon="upload" icon="upload"
> >
@@ -30,6 +92,13 @@ export default function DonationAddNews() {
label="Judul Berita" label="Judul Berita"
placeholder="Masukan judul berita" placeholder="Masukan judul berita"
required required
value={data.title}
onChangeText={(value) => {
setData({
...data,
title: value,
});
}}
/> />
<TextAreaCustom <TextAreaCustom
label="Deskripsi Berita" label="Deskripsi Berita"
@@ -37,12 +106,21 @@ export default function DonationAddNews() {
required required
showCount showCount
maxLength={1000} maxLength={1000}
value={data.deskripsi}
onChangeText={(value) => {
setData({
...data,
deskripsi: value,
});
}}
/> />
<Spacing /> <Spacing />
<ButtonCustom <ButtonCustom
disabled={!data.title || !data.deskripsi}
isLoading={isLoading}
onPress={() => { onPress={() => {
router.back(); handlerSubmit();
}} }}
> >
Simpan Simpan

View File

@@ -1,45 +1,88 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { import {
BackButton, BackButton,
BaseBox, BaseBox,
DrawerCustom, DrawerCustom,
Grid, Grid,
MenuDrawerDynamicGrid, LoaderCustom,
TextCustom, MenuDrawerDynamicGrid,
ViewWrapper TextCustom,
ViewWrapper,
} from "@/components"; } from "@/components";
import { IconPlus } from "@/components/_Icon"; import { IconPlus } from "@/components/_Icon";
import dayjs from "dayjs"; import { apiDonationGetNewsById } from "@/service/api-client/api-donation";
import { router, Stack, useLocalSearchParams } from "expo-router"; import { formatChatTime } from "@/utils/formatChatTime";
import { useState } from "react"; import {
router,
Stack,
useFocusEffect,
useLocalSearchParams,
} from "expo-router";
import _ from "lodash";
import { useCallback, useState } from "react";
export default function DonationRecapOfNews() { export default function DonationRecapOfNews() {
const { id } = useLocalSearchParams(); const { id } = useLocalSearchParams();
const [openDrawer, setOpenDrawer] = useState(false); 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 ( return (
<> <>
<Stack.Screen <Stack.Screen
options={{ options={{
title: "Daftar Kabar", title: "Daftar Kabar",
headerLeft: () => <BackButton />, }} headerLeft: () => <BackButton />,
}}
/> />
<ViewWrapper> <ViewWrapper>
{Array.from({ length: 15 }).map((_, index) => ( {loadList ? (
<BaseBox key={index} href={`/donation/${id}/(news)/${index}`}> <LoaderCustom />
<Grid> ) : _.isEmpty(list) ? (
<Grid.Col span={8}> <TextCustom align="center" color="gray">
<TextCustom truncate bold> Tidak ada kabar
Lorem ipsum dolor, sit amet consectetur adipisicing elit. </TextCustom>
</TextCustom> ) : (
</Grid.Col> list?.map((item: any, index: number) => (
<Grid.Col span={4} style={{ alignItems: "flex-end" }}> <BaseBox key={index} href={`/donation/[id]/(news)/${item.id}`}>
<TextCustom size="small"> <Grid>
{dayjs().format("DD MMM YYYY")} <Grid.Col span={8}>
</TextCustom> <TextCustom truncate bold>
</Grid.Col> {item?.title || "-"}
</Grid> </TextCustom>
</BaseBox> </Grid.Col>
))} <Grid.Col span={4} style={{ alignItems: "flex-end" }}>
<TextCustom size="small">
{formatChatTime(item?.createdAt)}
</TextCustom>
</Grid.Col>
</Grid>
</BaseBox>
))
)}
</ViewWrapper> </ViewWrapper>
<DrawerCustom <DrawerCustom

View File

@@ -1,21 +1,55 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { import {
BackButton, BackButton,
BaseBox, BaseBox,
DotButton, DotButton,
DrawerCustom, DrawerCustom,
Grid, Grid,
LoaderCustom,
MenuDrawerDynamicGrid, MenuDrawerDynamicGrid,
TextCustom, TextCustom,
ViewWrapper, ViewWrapper,
} from "@/components"; } from "@/components";
import { IconPlus } from "@/components/_Icon"; import { IconPlus } from "@/components/_Icon";
import dayjs from "dayjs"; import { apiDonationGetNewsById } from "@/service/api-client/api-donation";
import { router, Stack, useLocalSearchParams } from "expo-router"; import { formatChatTime } from "@/utils/formatChatTime";
import { useState } from "react"; import {
router,
Stack,
useFocusEffect,
useLocalSearchParams,
} from "expo-router";
import _ from "lodash";
import { useCallback, useState } from "react";
export default function DonationRecapOfNews() { export default function DonationRecapOfNews() {
const { id } = useLocalSearchParams(); const { id } = useLocalSearchParams();
const [openDrawer, setOpenDrawer] = useState(false); 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 ( return (
<> <>
@@ -27,20 +61,30 @@ export default function DonationRecapOfNews() {
}} }}
/> />
<ViewWrapper> <ViewWrapper>
{Array.from({ length: 15 }).map((_, index) => ( {loadList ? (
<BaseBox key={index} href={`/donation/${id}/(news)/${index}`}> <LoaderCustom />
<Grid> ) : _.isEmpty(list) ? (
<Grid.Col span={8}> <TextCustom align="center" color="gray">
<TextCustom truncate bold> Tidak ada kabar
Lorem ipsum dolor, sit amet consectetur adipisicing elit. </TextCustom>
</TextCustom> ) : (
</Grid.Col> list?.map((item: any, index: number) => (
<Grid.Col span={4} style={{ alignItems: "flex-end" }}> <BaseBox key={index} href={`/donation/[id]/(news)/${item.id}`}>
<TextCustom size="small">{dayjs().format("DD MMM YYYY")}</TextCustom> <Grid>
</Grid.Col> <Grid.Col span={8}>
</Grid> <TextCustom truncate bold>
</BaseBox> {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> </ViewWrapper>
<DrawerCustom <DrawerCustom

View File

@@ -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?.DonasiMaster_Bank?.norek}
</TextCustom>
</Grid.Col>
<Grid.Col
span={4}
style={{
alignItems: "flex-end",
}}
>
<CopyButton textToCopy={data?.DonasiMaster_Bank?.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>
</>
);
}

View File

@@ -1,13 +1,6 @@
import { import { BaseBox, StackCustom, TextCustom, ViewWrapper } from "@/components";
BaseBox, import MoneyTransferAnimation from "@/components/_ShareComponent/MoneyTransferAnimation";
Grid, import { View } from "react-native";
StackCustom,
TextCustom,
ViewWrapper,
} from "@/components";
import { MainColor } from "@/constants/color-palet";
import { Ionicons } from "@expo/vector-icons";
import { ActivityIndicator } from "react-native";
export default function DonationProcess() { export default function DonationProcess() {
return ( return (
@@ -16,13 +9,16 @@ export default function DonationProcess() {
<BaseBox> <BaseBox>
<StackCustom> <StackCustom>
<TextCustom align="center" bold> <TextCustom align="center" bold>
Admin sedang memproses transaksi donasimu Admin sedang memvalidasi data dan bukti transfer anda. Mohon
tunggu proses ini selesai.
</TextCustom> </TextCustom>
<ActivityIndicator size="large" color={MainColor.yellow} /> <View style={{ alignItems: "center", justifyContent: "center" }}>
<MoneyTransferAnimation />
</View>
</StackCustom> </StackCustom>
</BaseBox> </BaseBox>
<BaseBox> {/* <BaseBox>
<Grid> <Grid>
<Grid.Col span={10} style={{ justifyContent: "center" }}> <Grid.Col span={10} style={{ justifyContent: "center" }}>
<TextCustom size="small"> <TextCustom size="small">
@@ -38,7 +34,7 @@ export default function DonationProcess() {
/> />
</Grid.Col> </Grid.Col>
</Grid> </Grid>
</BaseBox> </BaseBox> */}
</ViewWrapper> </ViewWrapper>
</> </>
); );

View File

@@ -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>
</>
);
}

View File

@@ -9,15 +9,43 @@ import {
} from "@/components"; } from "@/components";
import { MainColor } from "@/constants/color-palet"; import { MainColor } from "@/constants/color-palet";
import { ICON_SIZE_SMALL } from "@/constants/constans-value"; 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 { Ionicons } from "@expo/vector-icons";
import AsyncStorage from "@react-native-async-storage/async-storage";
import { router, useLocalSearchParams } from "expo-router"; import { router, useLocalSearchParams } from "expo-router";
import { useState } from "react";
export default function InvestmentInputDonation() { export default function InvestmentInputDonation() {
const { id } = useLocalSearchParams(); 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 = ( const bottomComponent = (
<BoxButtonOnFooter> <BoxButtonOnFooter>
<ButtonCustom <ButtonCustom
onPress={() => router.replace(`/donation/${id}/select-bank`)} disabled={nominal < 10000 || nominal === 0}
onPress={() => {
handlerSubmit();
}}
> >
Lanjutan Lanjutan
</ButtonCustom> </ButtonCustom>
@@ -27,7 +55,7 @@ export default function InvestmentInputDonation() {
<> <>
<ViewWrapper footerComponent={bottomComponent}> <ViewWrapper footerComponent={bottomComponent}>
{listData.map((item, i) => ( {listData.map((item, i) => (
<BaseBox key={i}> <BaseBox key={i} onPress={() => setNominal(item.value)}>
<Grid> <Grid>
<Grid.Col span={8}> <Grid.Col span={8}>
<TextCustom bold size="large"> <TextCustom bold size="large">
@@ -48,9 +76,12 @@ export default function InvestmentInputDonation() {
<BaseBox> <BaseBox>
<TextInputCustom <TextInputCustom
keyboardType="numeric"
label="Nominal lainnya" label="Nominal lainnya"
placeholder="0" placeholder="0"
iconLeft="Rp." iconLeft="Rp."
value={displayJumlah}
onChangeText={(value) => handleChangeCurrency(value)}
/> />
<TextCustom size="small" color="gray"> <TextCustom size="small" color="gray">
Minimal donasi Rp. 10.000 Minimal donasi Rp. 10.000

View File

@@ -5,24 +5,84 @@ import {
ViewWrapper, ViewWrapper,
} from "@/components"; } from "@/components";
import { RadioCustom, RadioGroup } from "@/components/Radio/RadioCustom"; 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 { router, useLocalSearchParams } from "expo-router";
import { useState } from "react"; import _ from "lodash";
import { useEffect, useState } from "react";
export default function DonationSelectBank() { export default function DonationSelectBank() {
const { id, transaction } = useLocalSearchParams(); const { user } = useAuth();
const [value, setValue] = useState<any | number>(""); 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 = () => { const buttonSubmit = () => {
return ( return (
<> <>
<BoxButtonOnFooter> <BoxButtonOnFooter>
<ButtonCustom <ButtonCustom
onPress={() => isLoading={isLoading}
router.replace( disabled={!select}
`/(application)/(user)/donation/${id}/(transaction-flow)/${transaction}/invoice` onPress={() => handlerSubmit()}
)
}
> >
Pilih Pilih
</ButtonCustom> </ButtonCustom>
@@ -32,12 +92,14 @@ export default function DonationSelectBank() {
}; };
return ( return (
<ViewWrapper footerComponent={buttonSubmit()}> <ViewWrapper footerComponent={buttonSubmit()}>
<RadioGroup value={value} onChange={setValue}> <RadioGroup value={select} onChange={setSelect}>
{dummyMasterBank.map((item) => ( {_.isEmpty(listBank)
<BaseBox key={item.name}> ? []
<RadioCustom label={item.name} value={item.code} /> : listBank?.map((item: any, index: number) => (
</BaseBox> <BaseBox key={index}>
))} <RadioCustom label={item.namaBank} value={item.id} />
</BaseBox>
))}
</RadioGroup> </RadioGroup>
</ViewWrapper> </ViewWrapper>
); );

View File

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

View File

@@ -1,27 +1,42 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { import {
DummyLandscapeImage, DummyLandscapeImage,
StackCustom, StackCustom,
TextCustom, TextCustom,
ViewWrapper, ViewWrapper,
} from "@/components"; } 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() { export default function DonationDetailStory() {
const { id } = useLocalSearchParams(); 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 ( return (
<ViewWrapper> <ViewWrapper>
<StackCustom> <StackCustom>
<TextCustom> <TextCustom>{data?.pembukaan || "-"}</TextCustom>
Lorem {id} ipsum dolor, sit amet consectetur adipisicing elit. Fuga <DummyLandscapeImage imageId={data?.imageId} />
quasi nam nesciunt nisi corporis alias modi, pariatur sit totam rem <TextCustom>{data?.cerita || "-"}</TextCustom>
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>
</StackCustom> </StackCustom>
</ViewWrapper> </ViewWrapper>
); );

View File

@@ -1,7 +1,80 @@
import { ViewWrapper, StackCustom, InformationBox, TextInputCustom, Spacing, ButtonCustom } from "@/components"; /* eslint-disable react-hooks/exhaustive-deps */
import { router } from "expo-router"; 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() { 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 ( return (
<ViewWrapper> <ViewWrapper>
<StackCustom gap={"xs"}> <StackCustom gap={"xs"}>
@@ -10,17 +83,22 @@ export default function DonationEditRekening() {
label="Nama Bank" label="Nama Bank"
placeholder="Masukkan nama bank" placeholder="Masukkan nama bank"
required required
value={data.namaBank}
onChangeText={(value) => setData({ ...data, namaBank: value })}
/> />
<TextInputCustom <TextInputCustom
label="Nomor Rekening" label="Nomor Rekening"
placeholder="Masukkan nomor rekening" placeholder="Masukkan nomor rekening"
required required
value={data.rekening}
onChangeText={(value) => setData({ ...data, rekening: value })}
/> />
<Spacing /> <Spacing />
<ButtonCustom <ButtonCustom
isLoading={isLoading}
onPress={() => { onPress={() => {
router.back(); handlerSubmitUpdate();
}} }}
> >
Update Update
@@ -29,4 +107,4 @@ export default function DonationEditRekening() {
<Spacing /> <Spacing />
</ViewWrapper> </ViewWrapper>
); );
} }

View File

@@ -1,16 +1,97 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { import {
ButtonCenteredOnly, ButtonCenteredOnly,
ButtonCustom, ButtonCustom,
InformationBox, InformationBox,
LandscapeFrameUploaded, LandscapeFrameUploaded,
Spacing, Spacing,
StackCustom, StackCustom,
TextAreaCustom, TextAreaCustom,
ViewWrapper ViewWrapper,
} from "@/components"; } 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() { 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 ( return (
<ViewWrapper> <ViewWrapper>
<StackCustom gap={"xs"}> <StackCustom gap={"xs"}>
@@ -21,12 +102,23 @@ export default function DonationEditStory() {
required required
showCount showCount
maxLength={1000} maxLength={1000}
value={data?.pembukaan}
onChangeText={(value) => setData({ ...data, pembukaan: value })}
/> />
<LandscapeFrameUploaded /> <LandscapeFrameUploaded
image={
imageStory ? imageStory : API_IMAGE.GET({ fileId: data?.imageId })
}
/>
<ButtonCenteredOnly <ButtonCenteredOnly
onPress={() => { onPress={() => {
router.push("/(application)/(image)/take-picture/123"); pickFile({
allowedType: "image",
setImageUri: ({ uri }) => {
setImageStory(uri);
},
});
}} }}
icon="upload" icon="upload"
> >
@@ -39,12 +131,15 @@ export default function DonationEditStory() {
required required
showCount showCount
maxLength={1000} maxLength={1000}
value={data?.cerita}
onChangeText={(value) => setData({ ...data, cerita: value })}
/> />
<Spacing height={40} /> <Spacing height={40} />
<ButtonCustom <ButtonCustom
isLoading={isLoading}
onPress={() => { onPress={() => {
router.back(); handlerSubmitUpdate();
}} }}
> >
Update Update

View File

@@ -1,78 +1,275 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { import {
ButtonCenteredOnly, ButtonCenteredOnly,
ButtonCustom, ButtonCustom,
InformationBox, InformationBox,
LandscapeFrameUploaded, LandscapeFrameUploaded,
SelectCustom, LoaderCustom,
Spacing, SelectCustom,
StackCustom, Spacing,
TextInputCustom, StackCustom,
ViewWrapper, TextInputCustom,
ViewWrapper,
} from "@/components"; } from "@/components";
import { dummyDonasiDurasi } from "@/lib/dummy-data/donasi/durasi"; import API_IMAGE from "@/constants/api-storage";
import { dummyDonasiKategori } from "@/lib/dummy-data/donasi/kategori"; import DIRECTORY_ID from "@/constants/directory-id";
import { router } from "expo-router"; 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() { 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 ( return (
<ViewWrapper> <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 <LandscapeFrameUploaded
label="Judul Donasi" image={image ? image : API_IMAGE.GET({ fileId: data?.imageId })}
placeholder="Masukkan Judul Donasi" />
required <ButtonCenteredOnly
/> onPress={() => {
<TextInputCustom pickFile({
label="Target Donasi" setImageUri: ({ uri }) => {
placeholder="Masukkan Target Donasi" setImage(uri);
required },
keyboardType="numeric" allowedType: "image",
/> });
}}
icon="upload"
>
Upload
</ButtonCenteredOnly>
<Spacing />
<LandscapeFrameUploaded /> <SelectCustom
<ButtonCenteredOnly data={
onPress={() => { _.isEmpty(listCategory)
router.push("/(application)/(image)/take-picture/123"); ? []
}} : listCategory?.map((item) => ({
icon="upload" label: item.name,
> value: item.id,
Upload }))
</ButtonCenteredOnly> }
<Spacing /> label="Pilih Kategori Donasi"
placeholder="Pilih Kategori Donasi"
required
value={data?.donasiMaster_KategoriId}
onChange={(value: any) =>
setData({ ...data, donasiMaster_KategoriId: value })
}
/>
<SelectCustom <SelectCustom
data={dummyDonasiKategori.map((item) => ({ data={
label: item.label, _.isEmpty(listDuration)
value: item.value, ? []
}))} : listDuration?.map((item) => ({
onChange={(value) => console.log(value)} label: item.name + " hari",
label="Pilih Kategori Donasi" value: item.id,
placeholder="Pilih Kategori Donasi" }))
required }
/> label="Pilih Durasi Donasi"
placeholder="Pilih Durasi Donasi"
required
value={data?.donasiMaster_DurasiId}
onChange={(value: any) =>
setData({ ...data, donasiMaster_DurasiId: value })
}
/>
<SelectCustom <Spacing />
data={dummyDonasiDurasi.map((item) => ({ <ButtonCustom
label: item.label, isLoading={isLoading}
value: item.value, onPress={() => {
}))} handlerSubmitUpdate();
onChange={(value) => console.log(value)} }}
label="Pilih Durasi Donasi" >
placeholder="Pilih Durasi Donasi" Update
required </ButtonCustom>
/> </StackCustom>
)}
<Spacing />
<ButtonCustom
onPress={() => {
router.back();
}}
>
Update
</ButtonCustom>
</StackCustom>
<Spacing /> <Spacing />
</ViewWrapper> </ViewWrapper>
); );

View File

@@ -1,3 +1,4 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { import {
BackButton, BackButton,
BoxButtonOnFooter, BoxButtonOnFooter,
@@ -9,26 +10,75 @@ import {
ViewWrapper, ViewWrapper,
} from "@/components"; } from "@/components";
import { IconNews } from "@/components/_Icon"; import { IconNews } from "@/components/_Icon";
import { useAuth } from "@/hooks/use-auth";
import Donation_ComponentBoxDetailData from "@/screens/Donation/ComponentBoxDetailData"; import Donation_ComponentBoxDetailData from "@/screens/Donation/ComponentBoxDetailData";
import Donation_ComponentInfoFundrising from "@/screens/Donation/ComponentInfoFundrising"; import Donation_ComponentInfoFundrising from "@/screens/Donation/ComponentInfoFundrising";
import Donation_ComponentStoryFunrising from "@/screens/Donation/ComponentStoryFunrising"; import Donation_ComponentStoryFunrising from "@/screens/Donation/ComponentStoryFunrising";
import Donation_ProgressSection from "@/screens/Donation/ProgressSection"; import Donation_ProgressSection from "@/screens/Donation/ProgressSection";
import { router, Stack, useLocalSearchParams } from "expo-router"; import { apiDonationGetOne } from "@/service/api-client/api-donation";
import { useState } from "react"; import { countDownAndCondition } from "@/utils/countDownAndCondition";
import {
router,
Stack,
useFocusEffect,
useLocalSearchParams,
} from "expo-router";
import { useCallback, useEffect, useState } from "react";
export default function DonasiDetailBeranda() { export default function DonasiDetailBeranda() {
const { user } = useAuth();
const { id } = useLocalSearchParams(); const { id } = useLocalSearchParams();
const [openDrawer, setOpenDrawer] = useState(false); 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 = ( const buttonSection = (
<> <>
<BoxButtonOnFooter> <BoxButtonOnFooter>
<ButtonCustom <ButtonCustom
onPress={() => disabled={value?.reminder}
router.navigate(`/donation/${id}/(transaction-flow)`) onPress={() => router.navigate(`/donation/${id}/(transaction-flow)`)}
}
> >
Donasi {value?.reminder ? "Waktu berakhir" : "Donasi"}
</ButtonCustom> </ButtonCustom>
</BoxButtonOnFooter> </BoxButtonOnFooter>
</> </>
@@ -40,16 +90,25 @@ export default function DonasiDetailBeranda() {
options={{ options={{
title: `Detail Donasi`, title: `Detail Donasi`,
headerLeft: () => <BackButton />, headerLeft: () => <BackButton />,
headerRight: () => <DotButton onPress={() => setOpenDrawer(true)} />, headerRight: () =>
user?.id === data?.Author?.id ? (
<DotButton onPress={() => setOpenDrawer(true)} />
) : null,
}} }}
/> />
<ViewWrapper footerComponent={buttonSection}> <ViewWrapper footerComponent={buttonSection}>
<StackCustom> <StackCustom>
<Donation_ComponentBoxDetailData <Donation_ComponentBoxDetailData
sisaHari={value.sisa}
reminder={value.reminder}
data={data}
bottomSection={<Donation_ProgressSection id={id as string} />} bottomSection={<Donation_ProgressSection id={id as string} />}
/> />
<Donation_ComponentInfoFundrising id={id as string} /> <Donation_ComponentInfoFundrising dataAuthor={data?.Author} />
<Donation_ComponentStoryFunrising id={id as string} /> <Donation_ComponentStoryFunrising
id={id as string}
dataStory={data?.CeritaDonasi}
/>
</StackCustom> </StackCustom>
</ViewWrapper> </ViewWrapper>

View File

@@ -1,32 +1,68 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { import {
AvatarCustom, AvatarComp,
BaseBox, BaseBox,
ButtonCustom, ButtonCustom,
CenterCustom,
Grid, Grid,
LoaderCustom,
Spacing, Spacing,
TextCustom, TextCustom,
ViewWrapper ViewWrapper
} from "@/components"; } from "@/components";
import Donation_BoxPublish from "@/screens/Donation/BoxPublish"; 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() { 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 ( return (
<> <>
<ViewWrapper> <ViewWrapper>
<BaseBox> <BaseBox>
<Grid> <Grid>
<Grid.Col span={6} style={{ justifyContent: "center" }}> <Grid.Col span={6} style={{ justifyContent: "center" }}>
<CenterCustom> <View
<AvatarCustom size="lg" /> style={{
flexDirection: "column",
alignItems: "center",
gap: 10,
}}
>
<AvatarComp size="lg" fileId={data?.Profile?.imageId} />
<TextCustom bold size="large" truncate> <TextCustom bold size="large" truncate>
@Username @{data?.username}
</TextCustom> </TextCustom>
</CenterCustom> </View>
</Grid.Col> </Grid.Col>
<Grid.Col span={6} style={{ justifyContent: "center" }}> <Grid.Col span={6} style={{ justifyContent: "center" }}>
<ButtonCustom href={`/profile/1234`}> <ButtonCustom href={`/profile/${data?.Profile?.id}`}>
Kunjungi Profile Kunjungi Profile
</ButtonCustom> </ButtonCustom>
</Grid.Col> </Grid.Col>
@@ -35,9 +71,15 @@ export default function DonationInformationFunrising() {
<Spacing /> <Spacing />
{Array.from({ length: 10 }).map((_, index) => ( {loadList ? (
<Donation_BoxPublish key={index} id={index.toString()} /> <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> </ViewWrapper>
</> </>
); );

View File

@@ -1,3 +1,4 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { import {
ButtonCenteredOnly, ButtonCenteredOnly,
ButtonCustom, ButtonCustom,
@@ -9,9 +10,107 @@ import {
TextInputCustom, TextInputCustom,
ViewWrapper, ViewWrapper,
} from "@/components"; } 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() { 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 ( return (
<ViewWrapper> <ViewWrapper>
<StackCustom gap={"xs"}> <StackCustom gap={"xs"}>
@@ -22,6 +121,8 @@ export default function DonationCreateStory() {
required required
showCount showCount
maxLength={1000} maxLength={1000}
value={data.pembukaan}
onChangeText={(value) => setData({ ...data, pembukaan: value })}
/> />
<TextAreaCustom <TextAreaCustom
label="Tujuan Donasi" label="Tujuan Donasi"
@@ -29,12 +130,19 @@ export default function DonationCreateStory() {
required required
showCount showCount
maxLength={1000} maxLength={1000}
value={data.cerita}
onChangeText={(value) => setData({ ...data, cerita: value })}
/> />
<LandscapeFrameUploaded /> <LandscapeFrameUploaded image={imageStory || ""} />
<ButtonCenteredOnly <ButtonCenteredOnly
onPress={() => { onPress={() => {
router.push("/(application)/(image)/take-picture/123"); pickFile({
allowedType: "image",
setImageUri: ({ uri }) => {
setImageStory(uri);
},
});
}} }}
icon="upload" icon="upload"
> >
@@ -47,17 +155,23 @@ export default function DonationCreateStory() {
label="Nama Bank" label="Nama Bank"
placeholder="Masukkan nama bank" placeholder="Masukkan nama bank"
required required
value={data.namaBank}
onChangeText={(value) => setData({ ...data, namaBank: value })}
/> />
<TextInputCustom <TextInputCustom
label="Nomor Rekening" label="Nomor Rekening"
placeholder="Masukkan nomor rekening" placeholder="Masukkan nomor rekening"
required required
keyboardType="numeric"
value={data.rekening}
onChangeText={(value) => setData({ ...data, rekening: value })}
/> />
<Spacing /> <Spacing />
<ButtonCustom <ButtonCustom
isLoading={isLoading}
onPress={() => { onPress={() => {
router.replace(`/donation/(tabs)/status`); handlerSubmit();
}} }}
> >
Simpan Simpan

View File

@@ -3,17 +3,127 @@ import {
ButtonCustom, ButtonCustom,
InformationBox, InformationBox,
LandscapeFrameUploaded, LandscapeFrameUploaded,
LoaderCustom,
SelectCustom, SelectCustom,
Spacing, Spacing,
StackCustom, StackCustom,
TextInputCustom, TextInputCustom,
ViewWrapper, ViewWrapper,
} from "@/components"; } from "@/components";
import { dummyDonasiDurasi } from "@/lib/dummy-data/donasi/durasi"; import DIRECTORY_ID from "@/constants/directory-id";
import { dummyDonasiKategori } from "@/lib/dummy-data/donasi/kategori"; import { apiDonationCreate } from "@/service/api-client/api-donation";
import { router } from "expo-router"; 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() { 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 ( return (
<ViewWrapper> <ViewWrapper>
<StackCustom gap={"xs"}> <StackCustom gap={"xs"}>
@@ -23,18 +133,28 @@ export default function DonationCreate() {
label="Judul Donasi" label="Judul Donasi"
placeholder="Masukkan Judul Donasi" placeholder="Masukkan Judul Donasi"
required required
value={data.title}
onChangeText={(value) => setData({ ...data, title: value })}
/> />
<TextInputCustom <TextInputCustom
iconLeft="Rp."
label="Target Donasi" label="Target Donasi"
placeholder="Masukkan Target Donasi" placeholder="Masukkan Target Donasi"
required required
keyboardType="numeric" keyboardType="numeric"
value={displayTarget}
onChangeText={handleChangeCurrency("target")}
/> />
<LandscapeFrameUploaded /> <LandscapeFrameUploaded image={image || ""} />
<ButtonCenteredOnly <ButtonCenteredOnly
onPress={() => { onPress={() => {
router.push("/(application)/(image)/take-picture/123"); pickFile({
allowedType: "image",
setImageUri: ({ uri }) => {
setImage(uri);
},
});
}} }}
icon="upload" icon="upload"
> >
@@ -42,31 +162,52 @@ export default function DonationCreate() {
</ButtonCenteredOnly> </ButtonCenteredOnly>
<Spacing /> <Spacing />
<SelectCustom {loadList ? (
data={dummyDonasiKategori.map((item) => ({ <LoaderCustom />
label: item.label, ) : (
value: item.value, <SelectCustom
}))} data={
onChange={(value) => console.log(value)} _.isEmpty(listCategory)
label="Pilih Kategori Donasi" ? []
placeholder="Pilih Kategori Donasi" : listCategory?.map((item: any) => ({
required 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 /> <Spacing />
<ButtonCustom <ButtonCustom
isLoading={isLoading}
onPress={() => { onPress={() => {
router.replace("/donation/create-story"); handlerSubmit();
// router.push(`/donation/create-story?id=${"dasdsadsa"}`);
}} }}
> >
Selanjutnya Selanjutnya

View File

@@ -44,6 +44,8 @@ export default function EventBeranda() {
<TextCustom align="center">Belum ada event</TextCustom> <TextCustom align="center">Belum ada event</TextCustom>
) : ( ) : (
listData.map((item: any, index) => ( listData.map((item: any, index) => (
<Event_BoxPublishSection <Event_BoxPublishSection
key={index} key={index}
href={`/event/${item.id}/publish`} href={`/event/${item.id}/publish`}

View 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>
</>
);
};

View File

@@ -11,29 +11,30 @@ import {
apiEventGetOne, apiEventGetOne,
apiEventListOfParticipants, apiEventListOfParticipants,
} from "@/service/api-client/api-event"; } from "@/service/api-client/api-event";
import { useLocalSearchParams } from "expo-router"; import dayjs, { Dayjs } from "dayjs";
import { useEffect, useState } from "react"; import { useFocusEffect, useLocalSearchParams } from "expo-router";
import _ from "lodash";
import { useCallback, useState } from "react";
import { View } from "react-native"; import { View } from "react-native";
export default function EventListOfParticipants() { export default function EventListOfParticipants() {
const { id } = useLocalSearchParams(); const { id } = useLocalSearchParams();
const [startDate, setStartDate] = useState(); const [startDate, setStartDate] = useState<Dayjs | undefined>();
const [listData, setListData] = useState([]); const [listData, setListData] = useState<any[] | null>(null);
const [isLoadData, setIsLoadData] = useState(false); const [loadtData, setLoadData] = useState(false);
useEffect(() => { useFocusEffect(
handlerLoadData(); useCallback(() => {
}, [id]); handlerLoadData();
}, [id])
);
const handlerLoadData = () => { const handlerLoadData = () => {
try { try {
setIsLoadData(true);
onLoadData(); onLoadData();
onLoadList(); onLoadList();
} catch (error) { } catch (error) {
console.log("[ERROR]", error); console.log("[ERROR]", error);
} finally {
setIsLoadData(false);
} }
}; };
@@ -41,7 +42,8 @@ export default function EventListOfParticipants() {
try { try {
const response = await apiEventGetOne({ id: id as string }); const response = await apiEventGetOne({ id: id as string });
if (response.success) { if (response.success) {
setStartDate(response.data.tanggal); const date = dayjs(response.data.tanggal);
setStartDate(date);
} }
} catch (error) { } catch (error) {
console.log("[ERROR]", error); console.log("[ERROR]", error);
@@ -50,30 +52,36 @@ export default function EventListOfParticipants() {
const onLoadList = async () => { const onLoadList = async () => {
try { try {
setLoadData(true);
const response = await apiEventListOfParticipants({ id: id as string }); const response = await apiEventListOfParticipants({ id: id as string });
if (response.success) { if (response.success) {
setListData(response.data); setListData(response.data);
} }
} catch (error) { } catch (error) {
console.log("[ERROR]", error); console.log("[ERROR]", error);
} finally {
setLoadData(false);
} }
}; };
return ( return (
<ViewWrapper> <ViewWrapper>
{isLoadData ? ( {loadtData && !listData ? (
<LoaderCustom /> <LoaderCustom />
) : listData.length === 0 ? ( ) : _.isEmpty(listData) ? (
<TextCustom align="center">Belum ada peserta</TextCustom> <TextCustom align="center" color="gray">
Belum ada peserta
</TextCustom>
) : ( ) : (
listData.map((item: any, index: number) => ( listData?.map((item: any, index: number) => (
<BaseBox key={index}> <BaseBox key={index}>
<AvatarUsernameAndOtherComponent <AvatarUsernameAndOtherComponent
avatar={item?.User?.Profile?.imageId} avatar={item?.User?.Profile?.imageId}
name={item?.User?.username} name={item?.User?.username}
avatarHref={`/profile/${item?.User?.Profile?.id}`} avatarHref={`/profile/${item?.User?.Profile?.id}`}
rightComponent={ rightComponent={
new Date().getTime() > new Date(startDate as any).getTime() ? ( startDate && startDate.subtract(1, "hour").diff(dayjs()) < 0 ? (
<View <View
style={{ style={{
justifyContent: "flex-end", justifyContent: "flex-end",

View File

@@ -55,6 +55,8 @@ export default function EventDetailPublish() {
userId: user?.id as string, userId: user?.id as string,
}); });
console.log("[RES CHECK PARTICIPANTS]", responseCheckParticipants);
if ( if (
responseCheckParticipants.success && responseCheckParticipants.success &&
responseCheckParticipants.data responseCheckParticipants.data
@@ -69,6 +71,8 @@ export default function EventDetailPublish() {
} }
} }
console.log("[participans]", isParticipant);
const handlePress = (item: IMenuDrawerItem) => { const handlePress = (item: IMenuDrawerItem) => {
console.log("PATH ", item.path); console.log("PATH ", item.path);
router.navigate(item.path as any); router.navigate(item.path as any);

View File

@@ -191,7 +191,7 @@ export default function EventCreate() {
placeholder="Masukkan deskripsi event" placeholder="Masukkan deskripsi event"
required required
showCount showCount
maxLength={100} maxLength={1000}
onChangeText={(value: any) => onChangeText={(value: any) =>
setData({ ...data, deskripsi: value }) setData({ ...data, deskripsi: value })
} }

View File

@@ -1,25 +1,22 @@
/* eslint-disable react-hooks/exhaustive-deps */ /* eslint-disable react-hooks/exhaustive-deps */
import { import {
AlertCustom,
AvatarComp, AvatarComp,
AvatarCustom,
ButtonCustom, ButtonCustom,
CenterCustom, CenterCustom,
DrawerCustom, DrawerCustom,
FloatingButton,
Grid, Grid,
LoaderCustom, LoaderCustom,
StackCustom, StackCustom,
TextCustom, TextCustom,
ViewWrapper, ViewWrapper,
} from "@/components"; } from "@/components";
import { MainColor } from "@/constants/color-palet";
import { useAuth } from "@/hooks/use-auth"; import { useAuth } from "@/hooks/use-auth";
import Forum_BoxDetailSection from "@/screens/Forum/DiscussionBoxSection"; 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 Forum_MenuDrawerBerandaSection from "@/screens/Forum/MenuDrawerSection.tsx/MenuBeranda";
import { apiForumGetAll } from "@/service/api-client/api-forum"; import { apiForumGetAll } from "@/service/api-client/api-forum";
import { apiUser } from "@/service/api-client/api-user"; 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 _ from "lodash";
import { useCallback, useState } from "react"; import { useCallback, useState } from "react";
@@ -28,9 +25,6 @@ export default function Forumku() {
const { user } = useAuth(); const { user } = useAuth();
const [openDrawer, setOpenDrawer] = useState(false); const [openDrawer, setOpenDrawer] = useState(false);
const [status, setStatus] = useState(""); const [status, setStatus] = useState("");
const [alertStatus, setAlertStatus] = useState(false);
const [deleteAlert, setDeleteAlert] = useState(false);
const [listData, setListData] = useState<any | null>(null); const [listData, setListData] = useState<any | null>(null);
const [dataUser, setDataUser] = useState<any | null>(null); const [dataUser, setDataUser] = useState<any | null>(null);
const [loadingGetList, setLoadingGetList] = useState(false); const [loadingGetList, setLoadingGetList] = useState(false);
@@ -38,8 +32,8 @@ export default function Forumku() {
useFocusEffect( useFocusEffect(
useCallback(() => { useCallback(() => {
onLoadData(); onLoadData();
onLoadDataProfile(user?.id as string); onLoadDataProfile(id as string);
}, [user?.id]) }, [id])
); );
const onLoadDataProfile = async (id: string) => { const onLoadDataProfile = async (id: string) => {
@@ -71,7 +65,17 @@ export default function Forumku() {
return ( return (
<> <>
<ViewWrapper> <ViewWrapper
floatingButton={
user?.id === id && (
<FloatingButton
onPress={() =>
router.navigate("/(application)/(user)/forum/create")
}
/>
)
}
>
<StackCustom> <StackCustom>
<CenterCustom> <CenterCustom>
<AvatarComp <AvatarComp

View File

@@ -2,18 +2,46 @@ import {
BaseBox, BaseBox,
FloatingButton, FloatingButton,
Grid, Grid,
LoaderCustom,
ProgressCustom, ProgressCustom,
StackCustom, StackCustom,
TextCustom, TextCustom,
ViewWrapper, ViewWrapper,
} from "@/components"; } from "@/components";
import API_STRORAGE from "@/constants/base-url-api-strorage";
import DUMMY_IMAGE from "@/constants/dummy-image-value"; import DUMMY_IMAGE from "@/constants/dummy-image-value";
import { apiInvestmentGetAll } from "@/service/api-client/api-investment";
import { Ionicons } from "@expo/vector-icons";
import dayjs from "dayjs"; import dayjs from "dayjs";
import { Image } from "expo-image"; import { Image } from "expo-image";
import { router } from "expo-router"; import { router, useFocusEffect } from "expo-router";
import _ from "lodash";
import { useCallback, useState } from "react";
import { View } from "react-native"; import { View } from "react-native";
export default function InvestmentBursa() { 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();
console.log("[DATA LIST]", JSON.stringify(response.data, null, 2));
setList(response.data);
} catch (error) {
console.log("[ERROR]", error);
} finally {
setLoadingList(false);
}
};
return ( return (
<ViewWrapper <ViewWrapper
hideFooter hideFooter
@@ -21,40 +49,77 @@ export default function InvestmentBursa() {
<FloatingButton onPress={() => router.push("/investment/create")} /> <FloatingButton onPress={() => router.push("/investment/create")} />
} }
> >
{Array.from({ length: 10 }).map((_, index) => ( {loadingList ? (
<BaseBox key={index} paddingTop={7} paddingBottom={7} href={`/investment/${index}`}> <LoaderCustom />
<Grid> ) : _.isEmpty(list) ? (
<Grid.Col span={5}> <TextCustom>Belum ada data</TextCustom>
<Image ) : (
source={DUMMY_IMAGE.background} list?.map((item: any, index: number) => (
style={{ width: "auto", height: 100, borderRadius: 10 }} <BaseBox
/> key={index}
</Grid.Col> paddingTop={7}
<Grid.Col span={1}> paddingBottom={7}
<View /> href={`/investment/${item.id}`}
</Grid.Col> >
<Grid.Col span={6}> <Grid>
<StackCustom> <Grid.Col span={5}>
<TextCustom truncate={2}> <Image
Title here : Lorem ipsum dolor sit amet consectetur source={
adipisicing elit. Omnis, exercitationem, sequi enim quod item && item.imageId
distinctio maiores laudantium amet, quidem atque repellat sit ? API_STRORAGE.GET({ fileId: item.imageId })
vitae qui aliquam est veritatis laborum eum voluptatum totam! : DUMMY_IMAGE.background
</TextCustom> }
<ProgressCustom value={index % 5 * 20} size="lg" /> style={{ width: "auto", height: 100, borderRadius: 10 }}
<TextCustom> />
Sisa waktu: {dayjs().diff(dayjs(), "day")} hari </Grid.Col>
</TextCustom> <Grid.Col span={1}>
</StackCustom> <View />
</Grid.Col> </Grid.Col>
</Grid> <Grid.Col span={6}>
</BaseBox> <StackCustom>
))} <TextCustom truncate={2}>{item.title}</TextCustom>
<ProgressCustom
label={`${item.progress}%`}
value={item.progress}
size="lg"
/>
{Number(item?.pencarianInvestor) -
dayjs().diff(dayjs(item.countDown), "days") <=
0 ? (
<View
style={{
flexDirection: "row",
alignItems: "center",
gap: 5,
}}
>
<Ionicons
name="alert-circle-outline"
size={16}
color="red"
/>
<TextCustom color="red" size="small">
Periode Investasi Selesai
</TextCustom>
</View>
) : (
<TextCustom>
Sisa waktu:{" "}
{Number(item?.pencarianInvestor) -
dayjs().diff(dayjs(item.countDown), "days")}{" "}
hari
</TextCustom>
)}
</StackCustom>
</Grid.Col>
</Grid>
</BaseBox>
))
)}
</ViewWrapper> </ViewWrapper>
); );
} }
// <View style={{ padding: 20, gap: 16 }}> // <View style={{ padding: 20, gap: 16 }}>
// <TextCustom>Progress 70%</TextCustom> // <TextCustom>Progress 70%</TextCustom>
// <ProgressCustom value={70} color="primary" size="md" /> // <ProgressCustom value={70} color="primary" size="md" />
@@ -78,4 +143,4 @@ export default function InvestmentBursa() {
// <ProgressCustom value={90} color="warning" size="lg" showLabel={false} /> // <ProgressCustom value={90} color="warning" size="lg" showLabel={false} />
// <ProgressCustom value={null} color="error" size="sm" label="Loading..." /> // <ProgressCustom value={null} color="error" size="sm" label="Loading..." />
// </View>; // </View>;

View File

@@ -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 { dummyMasterStatus } from "@/lib/dummy-data/_master/status";
import Investment_StatusBox from "@/screens/Invesment/StatusBox"; 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() { export default function InvestmentPortofolio() {
const { user } = useAuth();
const [activeCategory, setActiveCategory] = useState<string | null>( const [activeCategory, setActiveCategory] = useState<string | null>(
"publish" "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) => { const handlePress = (item: any) => {
setActiveCategory(item.value); setActiveCategory(item.value);
// tambahkan logika lain seperti filter dsb. // tambahkan logika lain seperti filter dsb.
@@ -26,14 +61,20 @@ export default function InvestmentPortofolio() {
); );
return ( return (
<ViewWrapper headerComponent={scrollComponent} hideFooter> <ViewWrapper headerComponent={scrollComponent} hideFooter>
{Array.from({ length: 10 }).map((_, index) => ( {loadingList ? (
<Investment_StatusBox <LoaderCustom />
key={index} ) : _.isEmpty(listData) ? (
id={index.toString()} <TextCustom align="center">Tidak ada data {activeCategory}</TextCustom>
status={activeCategory as string} ) : (
href={`/investment/${index}/${activeCategory}/detail`} listData.map((item: any, index: number) => (
/> <Investment_StatusBox
))} key={index}
data={item}
status={activeCategory as string}
href={`/investment/${item.id}/${activeCategory}/detail`}
/>
))
)}
</ViewWrapper> </ViewWrapper>
); );
} }

View File

@@ -1,81 +1,123 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { import {
BadgeCustom, BadgeCustom,
BaseBox, BaseBox,
Grid, Grid,
LoaderCustom,
StackCustom, StackCustom,
TextCustom, TextCustom,
ViewWrapper, ViewWrapper,
} from "@/components"; } from "@/components";
import { dummyMasterStatusTransaction } from "@/lib/dummy-data/_master/status-transaction"; import { useAuth } from "@/hooks/use-auth";
import { apiInvestmentGetInvoice } from "@/service/api-client/api-investment";
import { GStyles } from "@/styles/global-styles"; import { GStyles } from "@/styles/global-styles";
import dayjs from "dayjs"; import { formatChatTime } from "@/utils/formatChatTime";
import { router } from "expo-router"; import { formatCurrencyDisplay } from "@/utils/formatCurrencyDisplay";
import { router, useFocusEffect } from "expo-router";
import _ from "lodash";
import { useCallback, useState } from "react";
import { View } from "react-native"; import { View } from "react-native";
export default function InvestmentTransaction() { export default function InvestmentTransaction() {
const randomStatusData = Array.from({ length: 10 }, () => { const { user } = useAuth();
const randomIndex = Math.floor( const [list, setList] = useState<any>([]);
Math.random() * dummyMasterStatusTransaction.length const [loadList, setLoadList] = useState<boolean>(false);
);
return dummyMasterStatusTransaction[randomIndex];
});
const handlePress = (value: string) => { useFocusEffect(
if (value === "menunggu") { useCallback(() => {
router.push(`/investment/${value}/(transaction-flow)/invoice`); onLoadList();
} else if (value === "proses") { }, [user?.id])
router.push(`/investment/${value}/(transaction-flow)/process`); );
} else if (value === "berhasil") {
router.push(`/investment/${value}/(transaction-flow)/success`); const onLoadList = async () => {
} else if (value === "gagal") { try {
router.push(`/investment/${value}/(transaction-flow)/failed`); 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 ( return (
<ViewWrapper hideFooter> <ViewWrapper hideFooter>
{randomStatusData.map((item, i) => ( {loadList ? (
<BaseBox <LoaderCustom />
key={i} ) : _.isEmpty(list) ? (
paddingTop={7} <TextCustom>Tidak ada data</TextCustom>
paddingBottom={7} ) : (
onPress={() => { list.map((item: any, i: number) => (
handlePress(item.value); <BaseBox
}} key={i}
> paddingTop={7}
<Grid> paddingBottom={7}
<Grid.Col span={6}> onPress={() => {
<StackCustom gap={"xs"}> handlePress({
<TextCustom truncate> id: item.id,
Title Investment: Lorem ipsum dolor sit amet consectetur status: _.lowerCase(item.statusInvoice),
adipisicing elit. Am culpa excepturi deleniti soluta animi });
porro amet ducimus. }}
</TextCustom> >
<TextCustom color="gray" size="small"> <Grid>
{dayjs().format("DD/MM/YYYY")} <Grid.Col span={6}>
</TextCustom> <StackCustom gap={"xs"}>
</StackCustom> <TextCustom truncate>{item?.title || "-"}</TextCustom>
</Grid.Col> <TextCustom color="gray" size="small">
<Grid.Col span={1}> {formatChatTime(item?.createdAt)}
<View /> </TextCustom>
</Grid.Col> </StackCustom>
<Grid.Col span={5} style={{ alignItems: "flex-end" }}> </Grid.Col>
<StackCustom gap={"xs"}> <Grid.Col span={1}>
<TextCustom bold truncate> <View />
Rp. 7.500.000 </Grid.Col>
</TextCustom> <Grid.Col span={5} style={{ alignItems: "flex-end" }}>
<BadgeCustom <StackCustom gap={"xs"}>
variant="light" <TextCustom bold truncate>
color={item.color} Rp. {formatCurrencyDisplay(item?.nominal) || "-"}
style={GStyles.alignSelfFlexEnd} </TextCustom>
> <BadgeCustom
{item.label} variant="light"
</BadgeCustom> color={handlerColor(_.lowerCase(item.statusInvoice))}
</StackCustom> style={GStyles.alignSelfFlexEnd}
</Grid.Col> >
</Grid> {item?.statusInvoice || "-"}
</BaseBox> </BadgeCustom>
))} </StackCustom>
</Grid.Col>
</Grid>
</BaseBox>
))
)}
</ViewWrapper> </ViewWrapper>
); );
} }

View File

@@ -7,17 +7,78 @@ import {
InformationBox, InformationBox,
Spacing, Spacing,
StackCustom, StackCustom,
TextCustom,
TextInputCustom, TextInputCustom,
ViewWrapper ViewWrapper,
} from "@/components"; } from "@/components";
import { MainColor } from "@/constants/color-palet"; 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 { 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() { 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 = ( const buttonFooter = (
<BoxButtonOnFooter> <BoxButtonOnFooter>
<ButtonCustom onPress={() => router.back()}>Simpan</ButtonCustom> <ButtonCustom
isLoading={isLoading}
disabled={!pdf || data.title.length <= 0}
onPress={handlerSubmit}
>
Simpan
</ButtonCustom>
</BoxButtonOnFooter> </BoxButtonOnFooter>
); );
@@ -31,22 +92,33 @@ export default function InvestmentAddDocument() {
label="Judul Dokumen" label="Judul Dokumen"
placeholder="Masukan judul dokumen" placeholder="Masukan judul dokumen"
required required
value={data.title}
onChangeText={(value) => setData({ ...data, title: value })}
/> />
<BaseBox> <BaseBox>
<CenterCustom> <CenterCustom>
<FontAwesome5 {pdf ? (
name="file-pdf" <TextCustom truncate>{pdf.name}</TextCustom>
size={30} ) : (
color={MainColor.disabled} <FontAwesome5
/> name="file-pdf"
size={30}
color={MainColor.disabled}
/>
)}
</CenterCustom> </CenterCustom>
</BaseBox> </BaseBox>
<ButtonCenteredOnly <ButtonCenteredOnly
icon="upload" icon="upload"
onPress={() => onPress={() =>
router.push("/(application)/(image)/take-picture/123") pickFile({
allowedType: "pdf",
setPdfUri(file) {
setPdf(file);
},
})
} }
> >
Upload Upload

View File

@@ -1,3 +1,4 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { import {
BaseBox, BaseBox,
BoxButtonOnFooter, BoxButtonOnFooter,
@@ -7,17 +8,99 @@ import {
InformationBox, InformationBox,
Spacing, Spacing,
StackCustom, StackCustom,
TextCustom,
TextInputCustom, TextInputCustom,
ViewWrapper, ViewWrapper,
} from "@/components"; } from "@/components";
import { MainColor } from "@/constants/color-palet"; import DIRECTORY_ID from "@/constants/directory-id";
import { FontAwesome5 } from "@expo/vector-icons"; import {
import { router } from "expo-router"; 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() { 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 = ( const buttonFooter = (
<BoxButtonOnFooter> <BoxButtonOnFooter>
<ButtonCustom onPress={() => router.back()}>Update</ButtonCustom> <ButtonCustom isLoading={isLoading} disabled={!pdf} onPress={handlerUpdate}>
Update
</ButtonCustom>
</BoxButtonOnFooter> </BoxButtonOnFooter>
); );
@@ -31,22 +114,29 @@ export default function InvestmentEditDocument() {
label="Judul Dokumen" label="Judul Dokumen"
placeholder="Masukan judul dokumen" placeholder="Masukan judul dokumen"
required required
value={data?.title}
onChangeText={(value) => setData({ ...data, title: value })}
/> />
<BaseBox> <BaseBox>
<CenterCustom> <CenterCustom>
<FontAwesome5 {pdf ? (
name="file-pdf" <TextCustom truncate>{pdf.name}</TextCustom>
size={30} ) : (
color={MainColor.disabled} <TextCustom truncate>{_.snakeCase(titleFile || "")}.pdf</TextCustom>
/> )}
</CenterCustom> </CenterCustom>
</BaseBox> </BaseBox>
<ButtonCenteredOnly <ButtonCenteredOnly
icon="upload" icon="upload"
onPress={() => onPress={() =>
router.push("/(application)/(image)/take-picture/123") pickFile({
allowedType: "pdf",
setPdfUri(file) {
setPdf(file);
},
})
} }
> >
Upload Upload

View File

@@ -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 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() { 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 ( return (
<ViewWrapper> <ViewWrapper>
{Array.from({ length: 10 }).map((_, index) => ( {loadList ? (
<Investment_BoxDetailDocument <LoaderCustom />
key={index} ) : _.isEmpty(list) ? (
title={`Judul Dokumen ${index + 1}`} <TextCustom align="center" color="gray">
href={`/(file)/${index + 1}`} Tidak ada data
/> </TextCustom>
))} ) : (
list?.map((item: any, index: number) => (
<Investment_BoxDetailDocument
key={index}
title={item.title}
href={`/(file)/${item.fileId}`}
/>
))
)}
</ViewWrapper> </ViewWrapper>
); );
} }

View File

@@ -1,23 +1,90 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { import {
AlertDefaultSystem, AlertDefaultSystem,
BackButton, BackButton,
DotButton, DotButton,
DrawerCustom, DrawerCustom,
LoaderCustom,
MenuDrawerDynamicGrid, MenuDrawerDynamicGrid,
TextCustom,
ViewWrapper, ViewWrapper,
} from "@/components"; } from "@/components";
import { IconEdit } from "@/components/_Icon"; import { IconEdit } from "@/components/_Icon";
import { MainColor } from "@/constants/color-palet"; import { MainColor } from "@/constants/color-palet";
import { ICON_SIZE_SMALL } from "@/constants/constans-value"; import { ICON_SIZE_SMALL } from "@/constants/constans-value";
import Investment_BoxDetailDocument from "@/screens/Invesment/Document/RecapBoxDetail"; 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 { AntDesign, Ionicons } from "@expo/vector-icons";
import { router, Stack, useLocalSearchParams } from "expo-router"; import {
import { useState } from "react"; 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() { export default function InvestmentRecapOfDocument() {
const { id } = useLocalSearchParams(); const { id } = useLocalSearchParams();
const [openDrawer, setOpenDrawer] = useState(false); const [openDrawer, setOpenDrawer] = useState(false);
const [openDrawerBox, setOpenDrawerBox] = useState(false); const [openDrawerBox, setOpenDrawerBox] = useState(false);
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 ( return (
<> <>
@@ -37,25 +104,36 @@ export default function InvestmentRecapOfDocument() {
/> />
<ViewWrapper> <ViewWrapper>
{Array.from({ length: 10 }).map((_, index) => ( {loadList ? (
<Investment_BoxDetailDocument <LoaderCustom />
key={index} ) : _.isEmpty(list) ? (
title={`Judul Dokumen ${index + 1}`} <TextCustom align="center" color="gray">
leftIcon={ Tidak ada data
<Ionicons </TextCustom>
name="ellipsis-horizontal-outline" ) : (
size={ICON_SIZE_SMALL} list?.map((item: any, index: number) => (
color={MainColor.white} <Investment_BoxDetailDocument
style={{ key={index}
zIndex: 10, title={item.title}
alignSelf: "flex-end", leftIcon={
}} <Ionicons
onPress={() => setOpenDrawerBox(true)} name="ellipsis-horizontal-outline"
/> size={ICON_SIZE_SMALL}
} color={MainColor.white}
href={`/(file)/${id}`} style={{
/> zIndex: 10,
))} alignSelf: "flex-end",
}}
onPress={() => {
setSelectId(item.id);
setOpenDrawerBox(true);
}}
/>
}
href={`/(file)/${item.fileId}`}
/>
))
)}
</ViewWrapper> </ViewWrapper>
{/* Drawer On Header */} {/* Drawer On Header */}
@@ -69,7 +147,7 @@ export default function InvestmentRecapOfDocument() {
{ {
icon: ( icon: (
<AntDesign <AntDesign
name="pluscircle" name="plus-circle"
size={ICON_SIZE_SMALL} size={ICON_SIZE_SMALL}
color={MainColor.white} color={MainColor.white}
/> />
@@ -96,7 +174,7 @@ export default function InvestmentRecapOfDocument() {
{ {
icon: <IconEdit />, icon: <IconEdit />,
label: "Edit Dokumen", label: "Edit Dokumen",
path: `/investment/${id}/(document)/edit-document`, path: `/investment/${selectId}/(document)/edit-document`,
}, },
{ {
icon: ( icon: (
@@ -119,12 +197,14 @@ export default function InvestmentRecapOfDocument() {
textLeft: "Batal", textLeft: "Batal",
textRight: "Hapus", textRight: "Hapus",
onPressRight: () => { onPressRight: () => {
setOpenDrawerBox(false); handlerDeleteDocument();
}, },
}); });
} else {
router.push(item.path as any);
} }
router.push(item.path as any);
setOpenDrawer(false); setOpenDrawerBox(false);
}} }}
/> />
</DrawerCustom> </DrawerCustom>

View File

@@ -1,3 +1,4 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { import {
AlertDefaultSystem, AlertDefaultSystem,
BackButton, BackButton,
@@ -8,37 +9,71 @@ import {
MenuDrawerDynamicGrid, MenuDrawerDynamicGrid,
StackCustom, StackCustom,
TextCustom, TextCustom,
ViewWrapper ViewWrapper,
} from "@/components"; } from "@/components";
import { IconTrash } from "@/components/_Icon/IconTrash"; import { IconTrash } from "@/components/_Icon/IconTrash";
import { router, Stack, useLocalSearchParams } from "expo-router"; import { useAuth } from "@/hooks/use-auth";
import { useState } from "react"; 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() { 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 [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 ( return (
<> <>
<Stack.Screen <Stack.Screen
options={{ options={{
title: "Detail Berita", title: "Detail Berita",
headerLeft: () => <BackButton />, headerLeft: () => <BackButton />,
headerRight: () => <DotButton onPress={() => setOpenDrawer(true)} />, headerRight: () =>
user?.id === data?.authorId && (
<DotButton onPress={() => setOpenDrawer(true)} />
),
}} }}
/> />
<ViewWrapper> <ViewWrapper>
<BaseBox> <BaseBox>
<StackCustom> <StackCustom>
<DummyLandscapeImage /> {data && data?.imageId && (
<DummyLandscapeImage imageId={data?.imageId || ""} />
)}
<TextCustom bold align="center" size="large"> <TextCustom bold align="center" size="large">
Judul Berita {news} Terbaru {(data && data?.title) || "-"}
</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!
</TextCustom> </TextCustom>
<TextCustom>{(data && data?.deskripsi) || "-"}</TextCustom>
</StackCustom> </StackCustom>
</BaseBox> </BaseBox>
</ViewWrapper> </ViewWrapper>
@@ -52,7 +87,7 @@ export default function InvestmentNews() {
data={[ data={[
{ {
label: "Hapus Berita", label: "Hapus Berita",
path: `/investment/${id}/add-news`, path: ``,
icon: <IconTrash />, icon: <IconTrash />,
color: "red", color: "red",
}, },
@@ -63,9 +98,26 @@ export default function InvestmentNews() {
message: "Apakah Anda yakin ingin menghapus berita ini?", message: "Apakah Anda yakin ingin menghapus berita ini?",
textLeft: "Batal", textLeft: "Batal",
textRight: "Hapus", textRight: "Hapus",
onPressRight: () => { onPressRight: async () => {
router.back(); try {
setOpenDrawer(false); 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);
}
}, },
}); });
}} }}

View File

@@ -9,17 +9,89 @@ import {
TextInputCustom, TextInputCustom,
ViewWrapper, ViewWrapper,
} from "@/components"; } 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() { 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 ( return (
<ViewWrapper> <ViewWrapper>
<StackCustom gap={"xs"}> <StackCustom gap={"xs"}>
<InformationBox text="Pengunggahan foto ke aplikasi bersifat opsional dan tidak diwajibkan, Anda dapat menyimpan berita tanpa mengunggah foto." /> <InformationBox text="Pengunggahan foto ke aplikasi bersifat opsional dan tidak diwajibkan, Anda dapat menyimpan berita tanpa mengunggah foto." />
<LandscapeFrameUploaded /> <LandscapeFrameUploaded image={image?.uri} />
<ButtonCenteredOnly <ButtonCenteredOnly
onPress={() => { onPress={() => {
router.push("/(application)/(image)/take-picture/123"); pickFile({
allowedType: "image",
setImageUri(file) {
setImage(file);
},
});
}} }}
icon="upload" icon="upload"
> >
@@ -30,6 +102,8 @@ export default function InvestmentAddNews() {
label="Judul Berita" label="Judul Berita"
placeholder="Masukan judul berita" placeholder="Masukan judul berita"
required required
value={data.title}
onChangeText={(value) => setData({ ...data, title: value })}
/> />
<TextAreaCustom <TextAreaCustom
label="Deskripsi Berita" label="Deskripsi Berita"
@@ -37,13 +111,11 @@ export default function InvestmentAddNews() {
required required
showCount showCount
maxLength={1000} maxLength={1000}
value={data.deskripsi}
onChangeText={(value) => setData({ ...data, deskripsi: value })}
/> />
<ButtonCustom <ButtonCustom isLoading={isLoading} onPress={handlerSubmit}>
onPress={() => {
router.back();
}}
>
Simpan Simpan
</ButtonCustom> </ButtonCustom>
</StackCustom> </StackCustom>

View File

@@ -1,18 +1,51 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { import {
BackButton, BackButton,
BaseBox, BaseBox,
DrawerCustom, DrawerCustom,
MenuDrawerDynamicGrid, LoaderCustom,
TextCustom, MenuDrawerDynamicGrid,
ViewWrapper TextCustom,
ViewWrapper,
} from "@/components"; } from "@/components";
import { IconPlus } from "@/components/_Icon"; import { IconPlus } from "@/components/_Icon";
import { router, Stack, useLocalSearchParams } from "expo-router"; import { apiInvestmentGetNews } from "@/service/api-client/api-investment";
import { useState } from "react"; import {
router,
Stack,
useFocusEffect,
useLocalSearchParams,
} from "expo-router";
import _ from "lodash";
import { useCallback, useState } from "react";
export default function InvestmentListOfNews() { export default function InvestmentListOfNews() {
const { id } = useLocalSearchParams(); const { id } = useLocalSearchParams();
const [openDrawer, setOpenDrawer] = useState(false); const [openDrawer, setOpenDrawer] = useState(false);
const [list, setList] = useState<any[] | null>(null);
const [loadList, setLoadList] = useState(false);
useFocusEffect(
useCallback(() => {
onLoadList();
}, [id])
);
const onLoadList = async () => {
try {
setLoadList(true);
const response = await apiInvestmentGetNews({
id: id as string,
category: "all-news",
});
setList(response.data);
} catch (error) {
console.log("[ERROR]", error);
} finally {
setLoadList(false);
}
};
return ( return (
<> <>
<Stack.Screen <Stack.Screen
@@ -22,16 +55,25 @@ export default function InvestmentListOfNews() {
// headerRight: () => <DotButton onPress={() => setOpenDrawer(true)} />, // headerRight: () => <DotButton onPress={() => setOpenDrawer(true)} />,
}} }}
/> />
<ViewWrapper> <ViewWrapper>
{Array.from({ length: 15 }).map((_, index) => ( {loadList ? (
<BaseBox <LoaderCustom />
key={index} ) : _.isEmpty(list) ? (
paddingBlock={5} <TextCustom align="center" color="gray">
href={`/investment/${id}/(news)/${index + 1}`} Tidak ada data
> </TextCustom>
<TextCustom bold>Berita Terbaru {index + 1}</TextCustom> ) : (
</BaseBox> list?.map((item: any, index: number) => (
))} <BaseBox
key={index}
paddingBlock={5}
href={`/investment/[id]/(news)/${item.id}`}
>
<TextCustom bold>{item.title}</TextCustom>
</BaseBox>
))
)}
</ViewWrapper> </ViewWrapper>
<DrawerCustom <DrawerCustom

View File

@@ -1,19 +1,53 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { import {
BackButton, BackButton,
BaseBox, BaseBox,
DotButton, DotButton,
DrawerCustom, DrawerCustom,
LoaderCustom,
MenuDrawerDynamicGrid, MenuDrawerDynamicGrid,
TextCustom, TextCustom,
ViewWrapper, ViewWrapper,
} from "@/components"; } from "@/components";
import { IconPlus } from "@/components/_Icon"; import { IconPlus } from "@/components/_Icon";
import { router, Stack, useLocalSearchParams } from "expo-router"; import { apiInvestmentGetNews } from "@/service/api-client/api-investment";
import { useState } from "react"; import {
router,
Stack,
useFocusEffect,
useLocalSearchParams,
} from "expo-router";
import _ from "lodash";
import { useCallback, useState } from "react";
export default function InvestmentRecapOfNews() { export default function InvestmentRecapOfNews() {
const { id } = useLocalSearchParams(); const { id } = useLocalSearchParams();
const [openDrawer, setOpenDrawer] = useState(false); const [openDrawer, setOpenDrawer] = useState(false);
const [list, setList] = useState<any[] | null>(null);
const [loadList, setLoadList] = useState(false);
useFocusEffect(
useCallback(() => {
onLoadList();
}, [id])
);
const onLoadList = async () => {
try {
setLoadList(true);
const response = await apiInvestmentGetNews({
id: id as string,
category: "all-news",
});
setList(response.data);
} catch (error) {
console.log("[ERROR]", error);
} finally {
setLoadList(false);
}
};
return ( return (
<> <>
<Stack.Screen <Stack.Screen
@@ -24,15 +58,23 @@ export default function InvestmentRecapOfNews() {
}} }}
/> />
<ViewWrapper> <ViewWrapper>
{Array.from({ length: 15 }).map((_, index) => ( {loadList ? (
<BaseBox <LoaderCustom />
key={index} ) : _.isEmpty(list) ? (
paddingBlock={5} <TextCustom align="center" color="gray">
href={`/investment/${id}/(news)/${index + 1}`} Tidak ada data
> </TextCustom>
<TextCustom bold>Berita Terbaru {index + 1}</TextCustom> ) : (
</BaseBox> list?.map((item: any, index: number) => (
))} <BaseBox
key={index}
paddingBlock={5}
href={`/investment/[id]/(news)/${item.id}`}
>
<TextCustom bold>{item.title}</TextCustom>
</BaseBox>
))
)}
</ViewWrapper> </ViewWrapper>
<DrawerCustom <DrawerCustom

View File

@@ -1,3 +1,4 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { import {
BaseBox, BaseBox,
BoxButtonOnFooter, BoxButtonOnFooter,
@@ -9,16 +10,89 @@ import {
TextInputCustom, TextInputCustom,
ViewWrapper, ViewWrapper,
} from "@/components"; } 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() { export default function InvestmentInvest() {
const { id } = useLocalSearchParams(); 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 = () => { const buttonSubmit = () => {
return ( return (
<> <>
<BoxButtonOnFooter> <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> </BoxButtonOnFooter>
</> </>
); );
@@ -34,7 +108,7 @@ export default function InvestmentInvest() {
<TextCustom>Sisa Lembar Saham</TextCustom> <TextCustom>Sisa Lembar Saham</TextCustom>
</Grid.Col> </Grid.Col>
<Grid.Col span={6} style={{ alignItems: "flex-end" }}> <Grid.Col span={6} style={{ alignItems: "flex-end" }}>
<TextCustom>3.000</TextCustom> <TextCustom>{data?.sisaLembar || "-"}</TextCustom>
</Grid.Col> </Grid.Col>
</Grid> </Grid>
<Grid> <Grid>
@@ -42,7 +116,7 @@ export default function InvestmentInvest() {
<TextCustom>Harga Per Lembar</TextCustom> <TextCustom>Harga Per Lembar</TextCustom>
</Grid.Col> </Grid.Col>
<Grid.Col span={6} style={{ alignItems: "flex-end" }}> <Grid.Col span={6} style={{ alignItems: "flex-end" }}>
<TextCustom>Rp. 1.000</TextCustom> <TextCustom>{data?.hargaLembar || "-"}</TextCustom>
</Grid.Col> </Grid.Col>
</Grid> </Grid>
<Grid> <Grid>
@@ -69,6 +143,10 @@ export default function InvestmentInvest() {
}} }}
placeholder="0" placeholder="0"
keyboardType="numeric" keyboardType="numeric"
value={jumlah.toString()}
onChangeText={(value) => {
handleTextChange(value);
}}
/> />
</Grid.Col> </Grid.Col>
</Grid> </Grid>
@@ -78,7 +156,7 @@ export default function InvestmentInvest() {
<TextCustom>Total Harga</TextCustom> <TextCustom>Total Harga</TextCustom>
</Grid.Col> </Grid.Col>
<Grid.Col span={6} style={{ alignItems: "flex-end" }}> <Grid.Col span={6} style={{ alignItems: "flex-end" }}>
<TextCustom>Rp. 1.000</TextCustom> <TextCustom> Rp. {formatCurrencyDisplay(total)}</TextCustom>
</Grid.Col> </Grid.Col>
</Grid> </Grid>
</StackCustom> </StackCustom>

View File

@@ -1,3 +1,4 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { import {
BaseBox, BaseBox,
ButtonCenteredOnly, ButtonCenteredOnly,
@@ -9,11 +10,96 @@ import {
TextCustom, TextCustom,
ViewWrapper, ViewWrapper,
} from "@/components"; } from "@/components";
import CopyButton from "@/components/Button/CoyButton";
import { MainColor } from "@/constants/color-palet"; 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() { export default function InvestmentInvoice() {
const { id } = useLocalSearchParams(); 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 ( return (
<> <>
<ViewWrapper> <ViewWrapper>
@@ -21,9 +107,23 @@ export default function InvestmentInvoice() {
<InformationBox text="Mohon transfer ke rekening dibawah" /> <InformationBox text="Mohon transfer ke rekening dibawah" />
<BaseBox> <BaseBox>
<StackCustom gap={"xs"}> <StackCustom gap={"xs"}>
<TextCustom>Nama BANK</TextCustom> <Grid>
<TextCustom>Nama Penerima</TextCustom> <Grid.Col span={4}>
<TextCustom>Bank</TextCustom>
</Grid.Col>
<Grid.Col span={8}>
<TextCustom>{data?.MasterBank?.namaBank}</TextCustom>
</Grid.Col>
</Grid>
<Spacing height={10} /> <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}> <BaseBox backgroundColor={MainColor.soft_darkblue}>
<Grid containerStyle={{ justifyContent: "center" }}> <Grid containerStyle={{ justifyContent: "center" }}>
@@ -34,7 +134,7 @@ export default function InvestmentInvoice() {
}} }}
> >
<TextCustom size="xlarge" bold color="yellow"> <TextCustom size="xlarge" bold color="yellow">
4567898765433567 {data?.MasterBank?.norek}
</TextCustom> </TextCustom>
</Grid.Col> </Grid.Col>
<Grid.Col <Grid.Col
@@ -43,7 +143,7 @@ export default function InvestmentInvoice() {
alignItems: "flex-end", alignItems: "flex-end",
}} }}
> >
<ButtonCustom>Salin</ButtonCustom> <CopyButton textToCopy={data?.MasterBank?.norek} />
</Grid.Col> </Grid.Col>
</Grid> </Grid>
</BaseBox> </BaseBox>
@@ -65,7 +165,7 @@ export default function InvestmentInvoice() {
}} }}
> >
<TextCustom size="xlarge" bold color="yellow"> <TextCustom size="xlarge" bold color="yellow">
Rp. 1.000.000 Rp. {formatCurrencyDisplay(data?.nominal)}
</TextCustom> </TextCustom>
</Grid.Col> </Grid.Col>
<Grid.Col <Grid.Col
@@ -74,7 +174,7 @@ export default function InvestmentInvoice() {
alignItems: "flex-end", alignItems: "flex-end",
}} }}
> >
<ButtonCustom>Salin</ButtonCustom> <CopyButton textToCopy={data?.nominal} />
</Grid.Col> </Grid.Col>
</Grid> </Grid>
</BaseBox> </BaseBox>
@@ -83,10 +183,37 @@ export default function InvestmentInvoice() {
<BaseBox> <BaseBox>
<StackCustom> <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 <ButtonCenteredOnly
onPress={() => { onPress={() => {
router.push("/(application)/(image)/take-picture/123"); pickFile({
allowedType: "image",
setImageUri(file: any) {
console.log("[IMAGE]", file);
setImage(file);
},
});
}} }}
icon="upload" icon="upload"
> >
@@ -96,14 +223,16 @@ export default function InvestmentInvoice() {
</BaseBox> </BaseBox>
<ButtonCustom <ButtonCustom
isLoading={isLoading}
disabled={!image}
onPress={() => { onPress={() => {
router.push(`/investment/${id}/(transaction-flow)/process`); handlerSubmitUpdate();
}} }}
> >
Saya Sudah Transfer Saya Sudah Transfer
</ButtonCustom> </ButtonCustom>
</StackCustom> </StackCustom>
<Spacing/> <Spacing />
</ViewWrapper> </ViewWrapper>
</> </>
); );

View File

@@ -1,13 +1,6 @@
import { import { BaseBox, StackCustom, TextCustom, ViewWrapper } from "@/components";
BaseBox, import MoneyTransferAnimation from "@/components/_ShareComponent/MoneyTransferAnimation";
Grid, import { View } from "react-native";
StackCustom,
TextCustom,
ViewWrapper,
} from "@/components";
import { MainColor } from "@/constants/color-palet";
import { Ionicons } from "@expo/vector-icons";
import { ActivityIndicator } from "react-native";
export default function InvestmentProcess() { export default function InvestmentProcess() {
return ( return (
@@ -16,21 +9,24 @@ export default function InvestmentProcess() {
<BaseBox> <BaseBox>
<StackCustom> <StackCustom>
<TextCustom align="center" bold> <TextCustom align="center" bold>
Admin sedang memproses transaksi investasimu Admin sedang memvalidasi data dan bukti transfer anda. Mohon
tunggu proses ini selesai.
</TextCustom> </TextCustom>
<ActivityIndicator size="large" color={MainColor.yellow} /> <View style={{ alignItems: "center", justifyContent: "center" }}>
<MoneyTransferAnimation />
</View>
</StackCustom> </StackCustom>
</BaseBox> </BaseBox>
<BaseBox> {/* <BaseBox>
<Grid> <Grid>
<Grid.Col span={10} style={{justifyContent: 'center'}}> <Grid.Col span={10} style={{ justifyContent: "center" }}>
<TextCustom size="small"> <TextCustom size="small">
Hubungi admin jika tidak kunjung di proses! Klik pada logo Hubungi admin jika tidak kunjung di proses! Klik pada logo
Whatsapp ini. Whatsapp ini.
</TextCustom> </TextCustom>
</Grid.Col> </Grid.Col>
<Grid.Col span={2} style={{alignItems: "flex-end"}}> <Grid.Col span={2} style={{ alignItems: "flex-end" }}>
<Ionicons <Ionicons
name="logo-whatsapp" name="logo-whatsapp"
size={50} size={50}
@@ -38,7 +34,7 @@ export default function InvestmentProcess() {
/> />
</Grid.Col> </Grid.Col>
</Grid> </Grid>
</BaseBox> </BaseBox> */}
</ViewWrapper> </ViewWrapper>
</> </>
); );

View File

@@ -5,35 +5,97 @@ import {
ViewWrapper, ViewWrapper,
} from "@/components"; } from "@/components";
import { RadioCustom, RadioGroup } from "@/components/Radio/RadioCustom"; 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 { router, useLocalSearchParams } from "expo-router";
import { useState } from "react"; import _ from "lodash";
import { useEffect, useState } from "react";
export default function InvestmentSelectBank() { export default function InvestmentSelectBank() {
const { user } = useAuth();
const { id } = useLocalSearchParams(); 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 = () => { const buttonSubmit = () => {
return ( return (
<> <>
<BoxButtonOnFooter> <BoxButtonOnFooter>
<ButtonCustom <ButtonCustom isLoading={isLoading} onPress={() => handlerSubmit()}>Pilih</ButtonCustom>
onPress={() => router.replace(`/investment/${id}/invoice`)}
>
Pilih
</ButtonCustom>
</BoxButtonOnFooter> </BoxButtonOnFooter>
</> </>
); );
}; };
return ( return (
<ViewWrapper footerComponent={buttonSubmit()}> <ViewWrapper footerComponent={buttonSubmit()}>
<RadioGroup value={value} onChange={setValue}> <RadioGroup value={select} onChange={setSelect}>
{dummyMasterBank.map((item) => ( {_.isEmpty(listBank)
<BaseBox key={item.name}> ? []
<RadioCustom label={item.name} value={item.code} /> : listBank?.map((item: any) => (
</BaseBox> <BaseBox key={item.id}>
))} <RadioCustom label={item.namaBank} value={item.id} />
</BaseBox>
))}
</RadioGroup> </RadioGroup>
</ViewWrapper> </ViewWrapper>
); );

View File

@@ -1,3 +1,4 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { import {
BackButton, BackButton,
DotButton, DotButton,
@@ -9,19 +10,47 @@ import { IconDocument, IconEdit, IconNews } from "@/components/_Icon";
import { IMenuDrawerItem } from "@/components/_Interface/types"; import { IMenuDrawerItem } from "@/components/_Interface/types";
import { MainColor } from "@/constants/color-palet"; import { MainColor } from "@/constants/color-palet";
import { ICON_SIZE_MEDIUM } from "@/constants/constans-value"; import { ICON_SIZE_MEDIUM } from "@/constants/constans-value";
import { useAuth } from "@/hooks/use-auth";
import Investment_ButtonInvestasiSection from "@/screens/Invesment/ButtonInvestasiSection"; import Investment_ButtonInvestasiSection from "@/screens/Invesment/ButtonInvestasiSection";
import Invesment_ComponentBoxOnBottomDetail from "@/screens/Invesment/ComponentBoxOnBottomDetail"; import Invesment_ComponentBoxOnBottomDetail from "@/screens/Invesment/ComponentBoxOnBottomDetail";
import Invesment_DetailDataPublishSection from "@/screens/Invesment/DetailDataPublishSection"; import Invesment_DetailDataPublishSection from "@/screens/Invesment/DetailDataPublishSection";
import { apiInvestmentGetOne } from "@/service/api-client/api-investment";
import { AntDesign, MaterialIcons } from "@expo/vector-icons"; 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 _ from "lodash";
import { useState } from "react"; import { useCallback, useState } from "react";
export default function InvestmentDetailStatus() { export default function InvestmentDetailStatus() {
const { user } = useAuth();
const { id, status } = useLocalSearchParams(); const { id, status } = useLocalSearchParams();
const [openDrawerDraft, setOpenDrawerDraft] = useState(false); const [openDrawerDraft, setOpenDrawerDraft] = useState(false);
const [openDrawerPublish, setOpenDrawerPublish] = 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) => { const handlePressDraft = (item: IMenuDrawerItem) => {
console.log("PATH >> ", item.path); console.log("PATH >> ", item.path);
router.navigate(item.path as any); router.navigate(item.path as any);
@@ -36,13 +65,14 @@ export default function InvestmentDetailStatus() {
const bottomSection = ( const bottomSection = (
<Invesment_ComponentBoxOnBottomDetail <Invesment_ComponentBoxOnBottomDetail
id={id as string} id={data?.id}
prospectusId={data?.prospektusFileId}
status={status as string} status={status as string}
/> />
); );
const buttonSection = ( const buttonSection = (
<Investment_ButtonInvestasiSection id={id as string} isMine={false} /> <Investment_ButtonInvestasiSection id={id as string} isMine={user?.id === data?.author?.id} />
); );
return ( return (
@@ -63,6 +93,7 @@ export default function InvestmentDetailStatus() {
<ViewWrapper> <ViewWrapper>
<Invesment_DetailDataPublishSection <Invesment_DetailDataPublishSection
status={status as string} status={status as string}
data={data}
bottomSection={bottomSection} bottomSection={bottomSection}
buttonSection={buttonSection} buttonSection={buttonSection}
/> />

View File

@@ -1,3 +1,4 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { import {
BaseBox, BaseBox,
BoxButtonOnFooter, BoxButtonOnFooter,
@@ -5,38 +6,149 @@ import {
ButtonCustom, ButtonCustom,
CenterCustom, CenterCustom,
InformationBox, InformationBox,
LoaderCustom,
Spacing, Spacing,
StackCustom, StackCustom,
TextCustom,
ViewWrapper, ViewWrapper,
} from "@/components"; } from "@/components";
import { MainColor } from "@/constants/color-palet"; import DIRECTORY_ID from "@/constants/directory-id";
import { FontAwesome5 } from "@expo/vector-icons"; import {
import { router } from "expo-router"; 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() { 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 = ( const buttonFooter = (
<BoxButtonOnFooter> <BoxButtonOnFooter>
<ButtonCustom onPress={() => router.back()}>Update</ButtonCustom> <ButtonCustom
disabled={pdf === null}
isLoading={isLoading}
onPress={handleSubmitUpdate}
>
Update
</ButtonCustom>
</BoxButtonOnFooter> </BoxButtonOnFooter>
); );
return ( return (
<ViewWrapper footerComponent={buttonFooter}> <ViewWrapper footerComponent={!loadingGet && buttonFooter}>
<StackCustom gap={"xs"}> <StackCustom gap={"xs"}>
<InformationBox text="File prospektus wajib untuk diupload, agar calon investor paham dengan prospek investasi yang akan anda jalankan kedepan." /> <InformationBox text="File prospektus wajib untuk diupload, agar calon investor paham dengan prospek investasi yang akan anda jalankan kedepan." />
<Spacing /> <Spacing />
<BaseBox> <BaseBox>
<CenterCustom> <CenterCustom>
<FontAwesome5 {loadingGet ? (
name="file-pdf" <LoaderCustom />
size={30} ) : pdf ? (
color={MainColor.disabled} <TextCustom truncate>{pdf.name}</TextCustom>
/> ) : (
<TextCustom truncate>
{_.snakeCase(data?.title || "").replace(/_/g, "-")}.pdf
</TextCustom>
)}
</CenterCustom> </CenterCustom>
</BaseBox> </BaseBox>
<ButtonCenteredOnly <ButtonCenteredOnly
disabled={loadingGet}
icon="upload" 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 Upload
</ButtonCenteredOnly> </ButtonCenteredOnly>

View File

@@ -1,40 +1,224 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { import {
ButtonCenteredOnly, ButtonCenteredOnly,
ButtonCustom, ButtonCustom,
InformationBox, InformationBox,
LandscapeFrameUploaded, LandscapeFrameUploaded,
LoaderCustom,
SelectCustom, SelectCustom,
Spacing, Spacing,
StackCustom, StackCustom,
TextInputCustom, TextInputCustom,
ViewWrapper ViewWrapper,
} from "@/components"; } from "@/components";
import dummyPembagianDeviden from "@/lib/dummy-data/investment/pembagian-deviden"; import API_STRORAGE from "@/constants/base-url-api-strorage";
import dummyListPencarianInvestor from "@/lib/dummy-data/investment/pencarian-investor"; import DIRECTORY_ID from "@/constants/directory-id";
import dummyPeriodeDeviden from "@/lib/dummy-data/investment/periode-deviden"; import {
import { router } from "expo-router"; apiInvestmentGetOne,
import { useState } from "react"; 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() { export default function InvestmentEdit() {
const [data, setData] = useState({ const { id } = useLocalSearchParams();
const [data, setData] = useState<IInvestment>({
title: "", title: "",
targetDana: 0, targetDana: "",
hargaPerLembar: 0, hargaLembar: "",
totalLembar: 0, totalLembar: "",
rasioKeuntungan: 0, roi: "",
pencarianInvestor: "", masterPencarianInvestorId: "",
periodeDeviden: "", masterPeriodeDevidenId: "",
pembagianDeviden: "", 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 ( return (
<ViewWrapper> <ViewWrapper>
<StackCustom gap={"xs"}> <StackCustom gap={"xs"}>
<InformationBox text="Gambar investasi bisa berupa ilustrasi, poster atau foto terkait investasi." /> <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 <ButtonCenteredOnly
icon="upload" icon="upload"
onPress={() => router.push("/take-picture/1")} onPress={() => {
pickFile({
setImageUri: ({ uri }) => {
setImage(uri);
},
allowedType: "image",
});
}}
> >
Upload Upload
</ButtonCenteredOnly> </ButtonCenteredOnly>
@@ -47,7 +231,7 @@ export default function InvestmentEdit() {
required required
placeholder="Judul" placeholder="Judul"
label="Judul" label="Judul"
value={data.title} value={data?.title}
onChangeText={(value) => setData({ ...data, title: value })} onChangeText={(value) => setData({ ...data, title: value })}
/> />
@@ -57,22 +241,8 @@ export default function InvestmentEdit() {
placeholder="0" placeholder="0"
label="Target Dana" label="Target Dana"
keyboardType="numeric" keyboardType="numeric"
onChangeText={(value) => onChangeText={handleChangeCurrency("targetDana")}
setData({ ...data, targetDana: Number(value) }) value={displayTargetDana}
}
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()}
/> />
<TextInputCustom <TextInputCustom
@@ -81,10 +251,8 @@ export default function InvestmentEdit() {
placeholder="0" placeholder="0"
label="Harga Per Lembar" label="Harga Per Lembar"
keyboardType="numeric" keyboardType="numeric"
onChangeText={(value) => onChangeText={handleChangeCurrency("hargaLembar")}
setData({ ...data, targetDana: Number(value) }) value={displayHargaPerLembar}
}
value={data.targetDana === 0 ? "" : data.targetDana.toString()}
/> />
<TextInputCustom <TextInputCustom
@@ -92,10 +260,8 @@ export default function InvestmentEdit() {
placeholder="0" placeholder="0"
label="Total Lembar" label="Total Lembar"
keyboardType="numeric" keyboardType="numeric"
onChangeText={(value) => onChangeText={(value) => setData({ ...data, totalLembar: value })}
setData({ ...data, totalLembar: Number(value) }) value={displayTotalLembar}
}
value={data.totalLembar === 0 ? "" : data.totalLembar.toString()}
/> />
<TextInputCustom <TextInputCustom
@@ -104,57 +270,78 @@ export default function InvestmentEdit() {
label="Rasio Keuntungan / ROI %" label="Rasio Keuntungan / ROI %"
placeholder="0" placeholder="0"
keyboardType="numeric" keyboardType="numeric"
onChangeText={(value) => onChangeText={(value) => setData({ ...data, roi: value })}
setData({ ...data, rasioKeuntungan: Number(value) }) value={data?.roi === "" ? "" : data?.roi}
}
value={
data.rasioKeuntungan === 0 ? "" : data.rasioKeuntungan.toString()
}
/> />
<SelectCustom {loadingMaster ? (
required <LoaderCustom />
placeholder="Pilih batas waktu" ) : (
label="Pencarian Investor" <SelectCustom
data={dummyListPencarianInvestor.map((item) => ({ required
label: item.name + `${" "}hari`, placeholder="Pilih batas waktu"
value: item.id, label="Pencarian Investor"
}))} data={
onChange={(value) => _.isEmpty(listPencarianInvestor)
setData({ ...data, pencarianInvestor: value as any }) ? []
} : listPencarianInvestor.map((item) => ({
value={data.pencarianInvestor} label: item.name + `${" "}hari`,
/> value: item.id,
}))
}
onChange={(value) =>
setData({ ...data, masterPencarianInvestorId: value as any })
}
value={data.masterPencarianInvestorId}
/>
)}
<SelectCustom {loadingMaster ? (
required <LoaderCustom />
placeholder="Pilih batas waktu" ) : (
label="Pilih Periode Deviden" <SelectCustom
data={dummyPeriodeDeviden.map((item) => ({ required
label: item.name, placeholder="Pilih batas waktu"
value: item.id, label="Pilih Periode Deviden"
}))} data={
onChange={(value) => _.isEmpty(listPeriodeDeviden)
setData({ ...data, periodeDeviden: value as any }) ? []
} : listPeriodeDeviden.map((item) => ({
value={data.periodeDeviden} 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 /> <Spacing />
<ButtonCustom onPress={() => router.replace("/investment/portofolio")}> <ButtonCustom isLoading={isLoading} onPress={handleSubmitUpdate}>
Simpan Simpan
</ButtonCustom> </ButtonCustom>
</StackCustom> </StackCustom>

View File

@@ -1,3 +1,4 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { import {
BackButton, BackButton,
DotButton, DotButton,
@@ -9,18 +10,45 @@ import { IconDocument, IconEdit, IconNews } from "@/components/_Icon";
import { IMenuDrawerItem } from "@/components/_Interface/types"; import { IMenuDrawerItem } from "@/components/_Interface/types";
import { MainColor } from "@/constants/color-palet"; import { MainColor } from "@/constants/color-palet";
import { ICON_SIZE_MEDIUM } from "@/constants/constans-value"; import { ICON_SIZE_MEDIUM } from "@/constants/constans-value";
import { useAuth } from "@/hooks/use-auth";
import Investment_ButtonInvestasiSection from "@/screens/Invesment/ButtonInvestasiSection"; import Investment_ButtonInvestasiSection from "@/screens/Invesment/ButtonInvestasiSection";
import Invesment_ComponentBoxOnBottomDetail from "@/screens/Invesment/ComponentBoxOnBottomDetail"; import Invesment_ComponentBoxOnBottomDetail from "@/screens/Invesment/ComponentBoxOnBottomDetail";
import Invesment_DetailDataPublishSection from "@/screens/Invesment/DetailDataPublishSection"; import Invesment_DetailDataPublishSection from "@/screens/Invesment/DetailDataPublishSection";
import { apiInvestmentGetOne } from "@/service/api-client/api-investment";
import { AntDesign, MaterialIcons } from "@expo/vector-icons"; 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 _ from "lodash";
import { useState } from "react"; import { useCallback, useState } from "react";
export default function InvestmentDetail() { export default function InvestmentDetail() {
const { user } = useAuth();
const { id, status } = useLocalSearchParams(); const { id, status } = useLocalSearchParams();
const [openDrawerDraft, setOpenDrawerDraft] = useState(false); const [openDrawerDraft, setOpenDrawerDraft] = useState(false);
const [openDrawerPublish, setOpenDrawerPublish] = 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) => { const handlePressDraft = (item: IMenuDrawerItem) => {
console.log("PATH >> ", item.path); console.log("PATH >> ", item.path);
@@ -37,11 +65,14 @@ export default function InvestmentDetail() {
const bottomSection = ( const bottomSection = (
<Invesment_ComponentBoxOnBottomDetail <Invesment_ComponentBoxOnBottomDetail
id={id as string} 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 ( return (
<> <>
@@ -61,6 +92,7 @@ export default function InvestmentDetail() {
<ViewWrapper> <ViewWrapper>
<Invesment_DetailDataPublishSection <Invesment_DetailDataPublishSection
status={"publish"} status={"publish"}
data={data}
bottomSection={bottomSection} bottomSection={bottomSection}
buttonSection={buttonSection} buttonSection={buttonSection}
/> />

View File

@@ -1,76 +1,235 @@
import { import {
BaseBox, BaseBox,
ButtonCenteredOnly, ButtonCenteredOnly,
ButtonCustom, ButtonCustom,
CenterCustom, CenterCustom,
InformationBox, InformationBox,
LandscapeFrameUploaded, LandscapeFrameUploaded,
SelectCustom, LoaderCustom,
Spacing, SelectCustom,
StackCustom, Spacing,
TextInputCustom, StackCustom,
ViewWrapper, TextCustom,
TextInputCustom,
ViewWrapper,
} from "@/components"; } from "@/components";
import { MainColor } from "@/constants/color-palet"; import { MainColor } from "@/constants/color-palet";
import dummyPembagianDeviden from "@/lib/dummy-data/investment/pembagian-deviden"; import DIRECTORY_ID from "@/constants/directory-id";
import dummyListPencarianInvestor from "@/lib/dummy-data/investment/pencarian-investor"; import { useAuth } from "@/hooks/use-auth";
import dummyPeriodeDeviden from "@/lib/dummy-data/investment/periode-deviden"; 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 { FontAwesome5 } from "@expo/vector-icons";
import { router } from "expo-router"; import { router, useFocusEffect } from "expo-router";
import { useState } from "react"; import _ from "lodash";
import { useCallback, useState } from "react";
import Toast from "react-native-toast-message";
export default function InvestmentCreate() { export default function InvestmentCreate() {
const { user } = useAuth();
const [data, setData] = useState({ const [data, setData] = useState({
title: "", title: "",
targetDana: 0, targetDana: "",
hargaPerLembar: 0, hargaPerLembar: "",
totalLembar: 0, totalLembar: "",
rasioKeuntungan: 0, rasioKeuntungan: "",
pencarianInvestor: "", pencarianInvestor: "",
periodeDeviden: "", periodeDeviden: "",
pembagianDeviden: "", 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 ( return (
<ViewWrapper> <ViewWrapper>
<StackCustom gap={"xs"}> <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." /> <InformationBox text="Gambar investasi bisa berupa ilustrasi, poster atau foto terkait investasi." />
<LandscapeFrameUploaded /> <LandscapeFrameUploaded image={image as string} />
<ButtonCenteredOnly <ButtonCenteredOnly
icon="upload" icon="upload"
onPress={() => router.push("/take-picture/1")} onPress={() => {
pickFile({
setImageUri: ({ uri }) => {
setImage(uri);
},
allowedType: "image",
});
}}
> >
Upload Upload
</ButtonCenteredOnly> </ButtonCenteredOnly>
<Spacing /> <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> <BaseBox>
<CenterCustom> <CenterCustom>
<FontAwesome5 {pdf ? (
name="file-pdf" <TextCustom>{pdf.name}</TextCustom>
size={30} ) : (
color={MainColor.disabled} <FontAwesome5
/> name="file-pdf"
size={30}
color={MainColor.disabled}
/>
)}
</CenterCustom> </CenterCustom>
</BaseBox> </BaseBox>
<ButtonCenteredOnly <ButtonCenteredOnly
icon="upload" icon="upload"
onPress={() => router.push("/take-picture/1")} onPress={() => {
pickFile({
setPdfUri: ({ uri, name, size }) => {
setPdf({ uri, name, size });
},
allowedType: "pdf",
});
}}
> >
Upload File Upload File
</ButtonCenteredOnly> </ButtonCenteredOnly>
@@ -90,22 +249,8 @@ export default function InvestmentCreate() {
placeholder="0" placeholder="0"
label="Target Dana" label="Target Dana"
keyboardType="numeric" keyboardType="numeric"
onChangeText={(value) => onChangeText={handleChangeCurrency("targetDana")}
setData({ ...data, targetDana: Number(value) }) value={displayTargetDana}
}
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()}
/> />
<TextInputCustom <TextInputCustom
@@ -114,22 +259,24 @@ export default function InvestmentCreate() {
placeholder="0" placeholder="0"
label="Harga Per Lembar" label="Harga Per Lembar"
keyboardType="numeric" keyboardType="numeric"
onChangeText={(value) => onChangeText={handleChangeCurrency("hargaPerLembar")}
setData({ ...data, targetDana: Number(value) }) value={displayHargaPerLembar}
}
value={data.targetDana === 0 ? "" : data.targetDana.toString()}
/> />
<TextInputCustom <StackCustom gap={0}>
required <TextInputCustom
placeholder="0" required
label="Total Lembar" placeholder="0"
keyboardType="numeric" label="Total Lembar"
onChangeText={(value) => keyboardType="numeric"
setData({ ...data, totalLembar: Number(value) }) // onChangeText={handleChangeCurrency("totalLembar")}
} value={displayTotalLembar}
value={data.totalLembar === 0 ? "" : data.totalLembar.toString()} />
/> <TextCustom size={"small"} color="gray">
*Total lembar dihitung dari, Target Dana / Harga Perlembar
</TextCustom>
</StackCustom>
<Spacing />
<TextInputCustom <TextInputCustom
required required
@@ -137,57 +284,80 @@ export default function InvestmentCreate() {
label="Rasio Keuntungan / ROI %" label="Rasio Keuntungan / ROI %"
placeholder="0" placeholder="0"
keyboardType="numeric" keyboardType="numeric"
onChangeText={(value) => onChangeText={(value) => setData({ ...data, rasioKeuntungan: value })}
setData({ ...data, rasioKeuntungan: Number(value) })
}
value={ value={
data.rasioKeuntungan === 0 ? "" : data.rasioKeuntungan.toString() data.rasioKeuntungan === "" ? "" : data.rasioKeuntungan.toString()
} }
/> />
<SelectCustom {loadingMaster ? (
required <LoaderCustom />
placeholder="Pilih batas waktu" ) : (
label="Pencarian Investor" <SelectCustom
data={dummyListPencarianInvestor.map((item) => ({ required
label: item.name + `${" "}hari`, placeholder="Pilih batas waktu"
value: item.id, label="Pencarian Investor"
}))} data={
onChange={(value) => _.isEmpty(listPencarianInvestor)
setData({ ...data, pencarianInvestor: value as any }) ? []
} : listPencarianInvestor.map((item) => ({
value={data.pencarianInvestor} label: item.name + `${" "}hari`,
/> value: item.id,
}))
}
onChange={(value) =>
setData({ ...data, pencarianInvestor: value as any })
}
value={data.pencarianInvestor}
/>
)}
<SelectCustom {loadingMaster ? (
required <LoaderCustom />
placeholder="Pilih batas waktu" ) : (
label="Pilih Periode Deviden" <SelectCustom
data={dummyPeriodeDeviden.map((item) => ({ required
label: item.name, placeholder="Pilih batas waktu"
value: item.id, label="Pilih Periode Deviden"
}))} data={
onChange={(value) => _.isEmpty(listPeriodeDeviden)
setData({ ...data, periodeDeviden: value as any }) ? []
} : listPeriodeDeviden.map((item) => ({
value={data.periodeDeviden} 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 /> <Spacing />
<ButtonCustom onPress={() => router.replace("/investment/portofolio")}> <ButtonCustom isLoading={isLoading} onPress={() => handleSubmit()}>
Simpan Simpan
</ButtonCustom> </ButtonCustom>
</StackCustom> </StackCustom>

View File

@@ -11,6 +11,7 @@ import {
} from "@/components"; } from "@/components";
import { IconEdit } from "@/components/_Icon"; import { IconEdit } from "@/components/_Icon";
import { IMenuDrawerItem } from "@/components/_Interface/types"; import { IMenuDrawerItem } from "@/components/_Interface/types";
import ReportBox from "@/components/Box/ReportBox";
import Job_BoxDetailSection from "@/screens/Job/BoxDetailSection"; import Job_BoxDetailSection from "@/screens/Job/BoxDetailSection";
import Job_ButtonStatusSection from "@/screens/Job/ButtonStatusSection"; import Job_ButtonStatusSection from "@/screens/Job/ButtonStatusSection";
import { apiJobGetOne } from "@/service/api-client/api-job"; import { apiJobGetOne } from "@/service/api-client/api-job";
@@ -70,7 +71,13 @@ export default function JobDetailStatus() {
<LoaderCustom /> <LoaderCustom />
) : ( ) : (
<> <>
<StackCustom> <StackCustom gap={"xs"}>
{data &&
data?.catatan &&
(status === "draft" || status === "rejected") && (
<ReportBox text={data?.catatan} />
)}
<Job_BoxDetailSection data={data} /> <Job_BoxDetailSection data={data} />
<Job_ButtonStatusSection <Job_ButtonStatusSection
id={id as string} id={id as string}

View File

@@ -1,23 +1,23 @@
/* eslint-disable react-hooks/exhaustive-deps */ /* eslint-disable react-hooks/exhaustive-deps */
import { import {
BaseBox, BaseBox,
ButtonCenteredOnly, ButtonCenteredOnly,
ButtonCustom, ButtonCustom,
DummyLandscapeImage, DummyLandscapeImage,
InformationBox, InformationBox,
LandscapeFrameUploaded, LandscapeFrameUploaded,
LoaderCustom, LoaderCustom,
Spacing, Spacing,
StackCustom, StackCustom,
TextAreaCustom, TextAreaCustom,
TextInputCustom, TextInputCustom,
ViewWrapper, ViewWrapper,
} from "@/components"; } from "@/components";
import DIRECTORY_ID from "@/constants/directory-id"; import DIRECTORY_ID from "@/constants/directory-id";
import { apiJobGetOne, apiJobUpdateData } from "@/service/api-client/api-job"; import { apiJobGetOne, apiJobUpdateData } from "@/service/api-client/api-job";
import { import {
deleteImageService, deleteFileService,
uploadImageService, uploadFileService,
} from "@/service/upload-service"; } from "@/service/upload-service";
import pickImage from "@/utils/pickImage"; import pickImage from "@/utils/pickImage";
import { router, useLocalSearchParams } from "expo-router"; import { router, useLocalSearchParams } from "expo-router";
@@ -69,7 +69,7 @@ export default function JobEdit() {
let newImageId = ""; let newImageId = "";
if (imageUri) { if (imageUri) {
const responseUploadImage = await uploadImageService({ const responseUploadImage = await uploadFileService({
imageUri: imageUri, imageUri: imageUri,
dirId: DIRECTORY_ID.job_image, dirId: DIRECTORY_ID.job_image,
}); });
@@ -80,7 +80,7 @@ export default function JobEdit() {
} }
if (data?.imageId) { if (data?.imageId) {
const responseDeleteImage = await deleteImageService({ const responseDeleteImage = await deleteFileService({
id: data.imageId, id: data.imageId,
}); });

View File

@@ -12,7 +12,7 @@ import {
import DIRECTORY_ID from "@/constants/directory-id"; import DIRECTORY_ID from "@/constants/directory-id";
import { useAuth } from "@/hooks/use-auth"; import { useAuth } from "@/hooks/use-auth";
import { apiJobCreate } from "@/service/api-client/api-job"; 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 pickImage from "@/utils/pickImage";
import { router } from "expo-router"; import { router } from "expo-router";
import { useState } from "react"; import { useState } from "react";
@@ -66,7 +66,7 @@ export default function JobCreate() {
return; return;
} }
const responseUploadImage = await uploadImageService({ const responseUploadImage = await uploadFileService({
imageUri: image, imageUri: image,
dirId: DIRECTORY_ID.job_image, dirId: DIRECTORY_ID.job_image,
}); });

View File

@@ -1,54 +1,226 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { import {
BaseBox,
BoxButtonOnFooter, BoxButtonOnFooter,
ButtonCenteredOnly, ButtonCenteredOnly,
ButtonCustom, ButtonCustom,
InformationBox, InformationBox,
LandscapeFrameUploaded, LandscapeFrameUploaded,
MapCustom,
Spacing, Spacing,
TextInputCustom, TextInputCustom,
ViewWrapper ViewWrapper,
} from "@/components"; } 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() { export default function MapsEdit() {
const { id } = useLocalSearchParams(); 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 = ( const buttonFooter = (
<BoxButtonOnFooter> <BoxButtonOnFooter>
<ButtonCustom <ButtonCustom
onPress={() => { disabled={!data.namePin}
console.log(`Simpan maps ${id}`); onPress={handleSubmit}
router.back() isLoading={isLoading}
}}
> >
Simpan Update
</ButtonCustom> </ButtonCustom>
</BoxButtonOnFooter> </BoxButtonOnFooter>
); );
return ( return (
<ViewWrapper footerComponent={buttonFooter}> <ViewWrapper footerComponent={buttonFooter}>
<InformationBox text="Tentukan lokasi pin map dengan menekan pada map." /> <InformationBox text="Tentukan lokasi pin map dengan menekan pada map." />
<BaseBox> <View style={[styles.container, { height: 400 }]}>
<MapCustom /> <MapView
</BaseBox> 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 <TextInputCustom
required required
label="Nama Pin" label="Nama Pin"
placeholder="Masukkan nama pin maps" placeholder="Masukkan nama pin maps"
value={data?.namePin}
onChangeText={(value) => setData({ ...data, namePin: value })}
/> />
<Spacing /> <Spacing />
<InformationBox text="Upload foto lokasi bisnis anda untuk ditampilkan dalam detail maps." /> <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 <ButtonCenteredOnly
icon="upload" icon="upload"
onPress={() => { onPress={() => {
console.log("Upload foto "); pickFile({
router.navigate(`/take-picture/${id}`); allowedType: "image",
setImageUri(file) {
setImage(file);
},
});
}} }}
> >
Upload Upload
@@ -57,3 +229,16 @@ export default function MapsEdit() {
</ViewWrapper> </ViewWrapper>
); );
} }
const styles = StyleSheet.create({
container: {
width: "100%",
backgroundColor: "#f5f5f5",
overflow: "hidden",
borderRadius: 8,
marginBottom: 20,
},
map: {
flex: 1,
},
});

View File

@@ -6,21 +6,96 @@ import {
InformationBox, InformationBox,
LandscapeFrameUploaded, LandscapeFrameUploaded,
Spacing, Spacing,
TextCustom,
TextInputCustom, TextInputCustom,
ViewWrapper, ViewWrapper,
} from "@/components"; } 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 { 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() { export default function MapsCreate() {
const { user } = useAuth();
const { id } = useLocalSearchParams(); 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 = ( const buttonFooter = (
<BoxButtonOnFooter> <BoxButtonOnFooter>
<ButtonCustom <ButtonCustom
onPress={() => { isLoading={isLoading}
console.log(`Simpan maps ${id}`); disabled={!selectedLocation || name === ""}
router.replace(`/portofolio/${id}`); onPress={handleSubmit}
}}
> >
Simpan Simpan
</ButtonCustom> </ButtonCustom>
@@ -30,25 +105,34 @@ export default function MapsCreate() {
<ViewWrapper footerComponent={buttonFooter}> <ViewWrapper footerComponent={buttonFooter}>
<InformationBox text="Tentukan lokasi pin map dengan menekan pada map." /> <InformationBox text="Tentukan lokasi pin map dengan menekan pada map." />
<BaseBox style={{ height: 400 }}> <BaseBox>
<TextCustom>Maps Her</TextCustom> <MapSelected
selectedLocation={selectedLocation as any}
setSelectedLocation={setSelectedLocation}
/>
</BaseBox> </BaseBox>
<TextInputCustom <TextInputCustom
required required
label="Nama Pin" label="Nama Pin"
placeholder="Masukkan nama pin maps" placeholder="Masukkan nama pin maps"
value={name}
onChangeText={setName}
/> />
<Spacing height={50} /> <Spacing height={50} />
<InformationBox text="Upload foto lokasi bisnis anda untuk ditampilkan dalam detail maps." /> <InformationBox text="Upload foto lokasi bisnis anda untuk ditampilkan dalam detail maps." />
<LandscapeFrameUploaded /> <LandscapeFrameUploaded image={image?.uri} />
<ButtonCenteredOnly <ButtonCenteredOnly
icon="upload" icon="upload"
onPress={() => { onPress={() => {
console.log("Upload foto "); pickFile({
router.navigate(`/take-picture/${id}`); allowedType: "image",
setImageUri(file) {
setImage(file);
},
});
}} }}
> >
Upload Upload

View File

@@ -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 { 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() { 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 ( return (
<ViewWrapper style={{ paddingInline: 0, paddingBlock: 0 }}> <>
{/* <MapCustom height={"100%"} /> */} <ViewWrapper style={{ paddingInline: 0, paddingBlock: 0 }}>
<View style={{ flex: 1 }}> {/* <MapCustom height={"100%"} /> */}
<MapView <View style={{ flex: 1 }}>
style={{ {loadList ? (
width: "100%", <MapView
height: "100%", initialRegion={defaultRegion}
}} style={{
/> width: "100%",
</View> height: "100%",
</ViewWrapper> }}
/>
) : (
<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>
</>
); );
} }

View File

@@ -1,9 +1,9 @@
import { import {
BaseBox, BaseBox,
BoxButtonOnFooter, BoxButtonOnFooter,
ButtonCenteredOnly, ButtonCenteredOnly,
ButtonCustom, ButtonCustom,
ViewWrapper ViewWrapper
} from "@/components"; } from "@/components";
import API_STRORAGE from "@/constants/base-url-api-strorage"; import API_STRORAGE from "@/constants/base-url-api-strorage";
import DIRECTORY_ID from "@/constants/directory-id"; import DIRECTORY_ID from "@/constants/directory-id";
@@ -11,10 +11,10 @@ import DUMMY_IMAGE from "@/constants/dummy-image-value";
import { useAuth } from "@/hooks/use-auth"; import { useAuth } from "@/hooks/use-auth";
import { apiFileDelete } from "@/service/api-client/api-file"; import { apiFileDelete } from "@/service/api-client/api-file";
import { import {
apiGetOnePortofolio, apiGetOnePortofolio,
apiUpdatePortofolio, apiUpdatePortofolio,
} from "@/service/api-client/api-portofolio"; } from "@/service/api-client/api-portofolio";
import { uploadImageService } from "@/service/upload-service"; import { uploadFileService } from "@/service/upload-service";
import pickImage from "@/utils/pickImage"; import pickImage from "@/utils/pickImage";
import { Image } from "expo-image"; import { Image } from "expo-image";
import { router, useLocalSearchParams } from "expo-router"; import { router, useLocalSearchParams } from "expo-router";
@@ -45,7 +45,7 @@ export default function PortofolioEditLogo() {
try { try {
setIsLoading(true); setIsLoading(true);
const response = await uploadImageService({ const response = await uploadFileService({
imageUri, imageUri,
dirId: DIRECTORY_ID.portofolio_logo, dirId: DIRECTORY_ID.portofolio_logo,
}); });

View File

@@ -1,8 +1,18 @@
/* eslint-disable react-hooks/exhaustive-deps */ /* eslint-disable react-hooks/exhaustive-deps */
import { DrawerCustom, LoaderCustom, Spacing, StackCustom } from "@/components"; import {
ButtonCustom,
DrawerCustom,
DummyLandscapeImage,
LoaderCustom,
Spacing,
StackCustom,
TextCustom,
} from "@/components";
import LeftButtonCustom from "@/components/Button/BackButton"; import LeftButtonCustom from "@/components/Button/BackButton";
import GridTwoView from "@/components/_ShareComponent/GridTwoView";
import ViewWrapper from "@/components/_ShareComponent/ViewWrapper"; import ViewWrapper from "@/components/_ShareComponent/ViewWrapper";
import { MainColor } from "@/constants/color-palet"; import { MainColor } from "@/constants/color-palet";
import { ICON_SIZE_SMALL } from "@/constants/constans-value";
import { useAuth } from "@/hooks/use-auth"; import { useAuth } from "@/hooks/use-auth";
import Portofolio_BusinessLocation from "@/screens/Portofolio/BusinessLocationSection"; import Portofolio_BusinessLocation from "@/screens/Portofolio/BusinessLocationSection";
import Portofolio_ButtonDelete from "@/screens/Portofolio/ButtonDelete"; import Portofolio_ButtonDelete from "@/screens/Portofolio/ButtonDelete";
@@ -13,19 +23,20 @@ import Portofolio_SocialMediaSection from "@/screens/Portofolio/SocialMediaSecti
import { apiGetOnePortofolio } from "@/service/api-client/api-portofolio"; import { apiGetOnePortofolio } from "@/service/api-client/api-portofolio";
import { apiUser } from "@/service/api-client/api-user"; import { apiUser } from "@/service/api-client/api-user";
import { GStyles } from "@/styles/global-styles"; import { GStyles } from "@/styles/global-styles";
import { Ionicons } from "@expo/vector-icons"; import { openInDeviceMaps } from "@/utils/openInDeviceMaps";
import { FontAwesome, Ionicons } from "@expo/vector-icons";
import { Stack, useFocusEffect, useLocalSearchParams } from "expo-router"; import { Stack, useFocusEffect, useLocalSearchParams } from "expo-router";
import { useCallback, useState } from "react"; import { useCallback, useState } from "react";
import { TouchableOpacity } from "react-native"; import { TouchableOpacity } from "react-native";
export default function Portofolio() { export default function Portofolio() {
const { user } = useAuth();
const { id } = useLocalSearchParams(); const { id } = useLocalSearchParams();
const [isDrawerOpen, setIsDrawerOpen] = useState(false); const [isDrawerOpen, setIsDrawerOpen] = useState(false);
const [isLoadingDelete, setIsLoadingDelete] = useState(false); const [isLoadingDelete, setIsLoadingDelete] = useState(false);
const [data, setData] = useState<any>(); const [data, setData] = useState<any>();
const [profileId, setProfileId] = useState<any>(); const [profileId, setProfileId] = useState<any>();
const [openDrawerLocation, setOpenDrawerLocation] = useState(false);
const { user } = useAuth();
const openDrawer = () => { const openDrawer = () => {
setIsDrawerOpen(true); setIsDrawerOpen(true);
@@ -43,19 +54,13 @@ export default function Portofolio() {
async function onLoadData(id: string) { async function onLoadData(id: string) {
const response = await apiGetOnePortofolio({ id: id }); const response = await apiGetOnePortofolio({ id: id });
console.log(
"[PROFILE ID]>>",
JSON.stringify(response.data.Profile.id, null, 2)
);
setData(response.data); setData(response.data);
} }
const onLoadUserByToken = async () => { const onLoadUserByToken = async () => {
const response = await apiUser(user?.id as string); const response = await apiUser(user?.id as string);
console.log(
"[PROFILE LOGIN]>>",
JSON.stringify(response.data?.Profile.id, null, 2)
);
setProfileId(response?.data?.Profile?.id); setProfileId(response?.data?.Profile?.id);
}; };
@@ -89,15 +94,21 @@ export default function Portofolio() {
data={data} data={data}
listSubBidang={data?.Portofolio_BidangDanSubBidangBisnis as any[]} listSubBidang={data?.Portofolio_BidangDanSubBidangBisnis as any[]}
/> />
<Portofolio_BusinessLocation /> <Portofolio_BusinessLocation
data={data?.BusinessMaps}
imageId={data?.logoId}
setOpenDrawerLocation={setOpenDrawerLocation}
/>
<Portofolio_SocialMediaSection <Portofolio_SocialMediaSection
data={data?.Portofolio_MediaSosial} data={data?.Portofolio_MediaSosial}
/> />
<Portofolio_ButtonDelete {data?.Profile?.id !== profileId ? null : (
id={id as string} <Portofolio_ButtonDelete
isLoadingDelete={isLoadingDelete} id={id as string}
setIsLoadingDelete={setIsLoadingDelete} isLoadingDelete={isLoadingDelete}
/> setIsLoadingDelete={setIsLoadingDelete}
/>
)}
<Spacing /> <Spacing />
</StackCustom> </StackCustom>
)} )}
@@ -110,10 +121,93 @@ export default function Portofolio() {
height={"auto"} height={"auto"}
> >
<Portofolio_MenuDrawerSection <Portofolio_MenuDrawerSection
drawerItems={drawerItemsPortofolio({ id: id as string })} drawerItems={drawerItemsPortofolio({
id: id as string,
maps: data?.BusinessMaps,
})}
setIsDrawerOpen={setIsDrawerOpen} setIsDrawerOpen={setIsDrawerOpen}
/> />
</DrawerCustom> </DrawerCustom>
{/* Drawer Lokasi */}
<DrawerCustom
isVisible={openDrawerLocation}
closeDrawer={() => setOpenDrawerLocation(false)}
height={"auto"}
>
<DummyLandscapeImage
height={200}
imageId={data?.BusinessMaps?.imageId}
/>
<Spacing />
<StackCustom gap={"xs"}>
<GridTwoView
spanLeft={2}
spanRight={10}
leftIcon={
<FontAwesome
name="building-o"
size={ICON_SIZE_SMALL}
color="white"
/>
}
rightIcon={<TextCustom>{data?.BusinessMaps?.namePin}</TextCustom>}
/>
<GridTwoView
spanLeft={2}
spanRight={10}
leftIcon={
<Ionicons
name="list-outline"
size={ICON_SIZE_SMALL}
color="white"
/>
}
rightIcon={
<TextCustom>{data?.MasterBidangBisnis?.name}</TextCustom>
}
/>
<GridTwoView
spanLeft={2}
spanRight={10}
leftIcon={
<Ionicons
name="call-outline"
size={ICON_SIZE_SMALL}
color="white"
/>
}
rightIcon={<TextCustom>{data?.tlpn}</TextCustom>}
/>
<GridTwoView
spanLeft={2}
spanRight={10}
leftIcon={
<Ionicons
name="location-outline"
size={ICON_SIZE_SMALL}
color="white"
/>
}
rightIcon={<TextCustom>{data?.alamatKantor}</TextCustom>}
/>
<Spacing />
<ButtonCustom
onPress={() => {
openInDeviceMaps({
latitude: data?.BusinessMaps?.latitude,
longitude: data?.BusinessMaps?.longitude,
title: data?.BusinessMaps?.namePin,
});
}}
>
Buka Maps
</ButtonCustom>
</StackCustom>
</DrawerCustom>
</> </>
); );
} }

View File

@@ -26,7 +26,7 @@ export default function Profile() {
const [dataToken, setDataToken] = useState<IProfile>(); const [dataToken, setDataToken] = useState<IProfile>();
const [listPortofolio, setListPortofolio] = useState<any[]>(); const [listPortofolio, setListPortofolio] = useState<any[]>();
const { logout, isAdmin, user } = useAuth(); const { token, logout, isAdmin, user, userData } = useAuth();
const openDrawer = () => { const openDrawer = () => {
setIsDrawerOpen(true); setIsDrawerOpen(true);
@@ -42,7 +42,8 @@ export default function Profile() {
onLoadPortofolio(id as string); onLoadPortofolio(id as string);
onLoadUserByToken(); onLoadUserByToken();
isUserCheck(); isUserCheck();
}, [id]) userData(token as string);
}, [id, token])
); );
const isUserCheck = () => { const isUserCheck = () => {
@@ -136,10 +137,7 @@ const ButtonnDot = ({
}) => { }) => {
const isId = id === undefined || id === null; const isId = id === undefined || id === null;
console.log("ID CHECK", id);
if (isId) { if (isId) {
console.log("ID UNDEFINED", id);
return ( return (
<> <>
<TouchableOpacity onPress={logout}> <TouchableOpacity onPress={logout}>

View File

@@ -1,8 +1,8 @@
import { import {
BaseBox, BaseBox,
BoxButtonOnFooter, BoxButtonOnFooter,
ButtonCenteredOnly, ButtonCenteredOnly,
ButtonCustom, ButtonCustom,
} from "@/components"; } from "@/components";
import ViewWrapper from "@/components/_ShareComponent/ViewWrapper"; import ViewWrapper from "@/components/_ShareComponent/ViewWrapper";
import API_STRORAGE from "@/constants/base-url-api-strorage"; import API_STRORAGE from "@/constants/base-url-api-strorage";
@@ -11,7 +11,7 @@ import DUMMY_IMAGE from "@/constants/dummy-image-value";
import { useAuth } from "@/hooks/use-auth"; import { useAuth } from "@/hooks/use-auth";
import { apiFileDelete } from "@/service/api-client/api-file"; import { apiFileDelete } from "@/service/api-client/api-file";
import { apiProfile, apiUpdateProfile } from "@/service/api-client/api-profile"; import { apiProfile, apiUpdateProfile } from "@/service/api-client/api-profile";
import { uploadImageService } from "@/service/upload-service"; import { uploadFileService } from "@/service/upload-service";
import { IProfile } from "@/types/Type-Profile"; import { IProfile } from "@/types/Type-Profile";
import pickImage from "@/utils/pickImage"; import pickImage from "@/utils/pickImage";
import { router, useFocusEffect, useLocalSearchParams } from "expo-router"; import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
@@ -45,7 +45,7 @@ export default function UpdateBackgroundProfile() {
try { try {
setIsLoading(true); setIsLoading(true);
const response = await uploadImageService({ const response = await uploadFileService({
imageUri, imageUri,
dirId: DIRECTORY_ID.profile_background, dirId: DIRECTORY_ID.profile_background,
}); });

View File

@@ -8,16 +8,16 @@ import ViewWrapper from "@/components/_ShareComponent/ViewWrapper";
import API_STRORAGE from "@/constants/base-url-api-strorage"; import API_STRORAGE from "@/constants/base-url-api-strorage";
import DIRECTORY_ID from "@/constants/directory-id"; import DIRECTORY_ID from "@/constants/directory-id";
import DUMMY_IMAGE from "@/constants/dummy-image-value"; import DUMMY_IMAGE from "@/constants/dummy-image-value";
import { useAuth } from "@/hooks/use-auth";
import { apiFileDelete } from "@/service/api-client/api-file";
import { apiProfile, apiUpdateProfile } from "@/service/api-client/api-profile"; import { apiProfile, apiUpdateProfile } from "@/service/api-client/api-profile";
import { uploadImageService } from "@/service/upload-service"; import { uploadFileService } from "@/service/upload-service";
import { IProfile } from "@/types/Type-Profile"; import { IProfile } from "@/types/Type-Profile";
import pickImage from "@/utils/pickImage"; import pickImage from "@/utils/pickImage";
import { router, useFocusEffect, useLocalSearchParams } from "expo-router"; import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
import { useCallback, useState } from "react"; import { useCallback, useState } from "react";
import { Image } from "react-native"; import { Image } from "react-native";
import Toast from "react-native-toast-message"; import Toast from "react-native-toast-message";
import { useAuth } from "@/hooks/use-auth";
import { apiFileDelete } from "@/service/api-client/api-file";
export default function UpdatePhotoProfile() { export default function UpdatePhotoProfile() {
const { id } = useLocalSearchParams(); const { id } = useLocalSearchParams();
@@ -46,7 +46,7 @@ export default function UpdatePhotoProfile() {
try { try {
setIsLoading(true); setIsLoading(true);
const response = await uploadImageService({ const response = await uploadFileService({
imageUri, imageUri,
dirId: DIRECTORY_ID.profile_foto, dirId: DIRECTORY_ID.profile_foto,
}); });

View File

@@ -1,12 +1,12 @@
import { import {
BaseBox, BaseBox,
ButtonCenteredOnly, ButtonCenteredOnly,
ButtonCustom, ButtonCustom,
SelectCustom, SelectCustom,
Spacing, Spacing,
StackCustom, StackCustom,
TextInputCustom, TextInputCustom,
ViewWrapper, ViewWrapper,
} from "@/components"; } from "@/components";
import BoxButtonOnFooter from "@/components/Box/BoxButtonOnFooter"; import BoxButtonOnFooter from "@/components/Box/BoxButtonOnFooter";
import InformationBox from "@/components/Box/InformationBox"; import InformationBox from "@/components/Box/InformationBox";
@@ -15,7 +15,7 @@ import DUMMY_IMAGE from "@/constants/dummy-image-value";
import { useAuth } from "@/hooks/use-auth"; import { useAuth } from "@/hooks/use-auth";
import { apiCreateProfile } from "@/service/api-client/api-profile"; import { apiCreateProfile } from "@/service/api-client/api-profile";
import { apiValidationEmail } from "@/service/api-client/api-validation"; import { apiValidationEmail } from "@/service/api-client/api-validation";
import { uploadImageService } from "@/service/upload-service"; import { uploadFileService } from "@/service/upload-service";
import pickImage from "@/utils/pickImage"; import pickImage from "@/utils/pickImage";
import { router } from "expo-router"; import { router } from "expo-router";
import { useState } from "react"; import { useState } from "react";
@@ -69,7 +69,7 @@ export default function CreateProfile() {
if (imagePhoto) { if (imagePhoto) {
try { try {
const responseUploadPhoto = await uploadImageService({ const responseUploadPhoto = await uploadFileService({
imageUri: imagePhoto, imageUri: imagePhoto,
dirId: DIRECTORY_ID.profile_foto, dirId: DIRECTORY_ID.profile_foto,
}); });
@@ -90,7 +90,7 @@ export default function CreateProfile() {
if (imageBackground) { if (imageBackground) {
try { try {
const responseUploadBackground = await uploadImageService({ const responseUploadBackground = await uploadFileService({
imageUri: imageBackground, imageUri: imageBackground,
dirId: DIRECTORY_ID.profile_background, dirId: DIRECTORY_ID.profile_background,
}); });

View File

@@ -8,11 +8,13 @@ import {
LoaderCustom, LoaderCustom,
MenuDrawerDynamicGrid, MenuDrawerDynamicGrid,
Spacing, Spacing,
StackCustom,
TextCustom, TextCustom,
ViewWrapper, ViewWrapper,
} from "@/components"; } from "@/components";
import { IconArchive, IconContribution, IconEdit } from "@/components/_Icon"; import { IconArchive, IconContribution, IconEdit } from "@/components/_Icon";
import { IMenuDrawerItem } from "@/components/_Interface/types"; import { IMenuDrawerItem } from "@/components/_Interface/types";
import ReportBox from "@/components/Box/ReportBox";
import Voting_BoxDetailHasilVotingSection from "@/screens/Voting/BoxDetailHasilVotingSection"; import Voting_BoxDetailHasilVotingSection from "@/screens/Voting/BoxDetailHasilVotingSection";
import { Voting_BoxDetailSection } from "@/screens/Voting/BoxDetailSection"; import { Voting_BoxDetailSection } from "@/screens/Voting/BoxDetailSection";
import Voting_ButtonStatusSection from "@/screens/Voting/ButtonStatusSection"; import Voting_ButtonStatusSection from "@/screens/Voting/ButtonStatusSection";
@@ -49,6 +51,8 @@ export default function VotingDetailStatus() {
setLoadingGetData(true); setLoadingGetData(true);
const response = await apiVotingGetOne({ id: id as string }); const response = await apiVotingGetOne({ id: id as string });
console.log("[DATA BY ID]", JSON.stringify(response, null, 2));
if (response.success) { if (response.success) {
setData(response.data); setData(response.data);
} }
@@ -127,6 +131,13 @@ export default function VotingDetailStatus() {
</BaseBox> </BaseBox>
)} )}
<Spacing height={0} /> <Spacing height={0} />
{data &&
data?.catatan &&
(status === "draft" || status === "rejected") && (
<ReportBox text={data?.catatan} />
)}
<Voting_BoxDetailSection data={data as any} /> <Voting_BoxDetailSection data={data as any} />
{status === "publish" ? ( {status === "publish" ? (
<Voting_BoxDetailHasilVotingSection <Voting_BoxDetailHasilVotingSection

View File

@@ -9,6 +9,7 @@ import {
} from "@/components"; } from "@/components";
import { ICON_SIZE_BUTTON } from "@/constants/constans-value"; import { ICON_SIZE_BUTTON } from "@/constants/constans-value";
import { useAuth } from "@/hooks/use-auth"; import { useAuth } from "@/hooks/use-auth";
import { apiUser } from "@/service/api-client/api-user";
import { Ionicons } from "@expo/vector-icons"; import { Ionicons } from "@expo/vector-icons";
import { router } from "expo-router"; import { router } from "expo-router";
import Toast from "react-native-toast-message"; import Toast from "react-native-toast-message";
@@ -19,12 +20,25 @@ export default function WaitingRoom() {
async function handleCheck() { async function handleCheck() {
try { try {
const response = await userData(token as string); const response = await userData(token as string);
if (response.active) { if (response.active) {
Toast.show({ const checkProfile = await apiUser(response.id);
type: "success",
text1: "Akun anda telah aktif", // text2: "Anda berhasil login", if (checkProfile?.data?.Profile) {
}); Toast.show({
router.replace(`/(application)/(user)/profile/create`); type: "success",
text1: "Akun anda telah aktif kembali", // text2: "Anda berhasil login",
});
router.replace(`/(application)/(user)/home`);
} else {
Toast.show({
type: "success",
text1: "Akun anda telah aktif", // text2: "Anda berhasil login",
});
router.replace(`/(application)/(user)/profile/create`);
}
// router.replace(`/(application)/(user)/profile/create`);
} else { } else {
Toast.show({ Toast.show({
type: "error", type: "error",

View File

@@ -15,7 +15,10 @@ import {
ICON_SIZE_XLARGE, ICON_SIZE_XLARGE,
} from "@/constants/constans-value"; } from "@/constants/constans-value";
import { useAuth } from "@/hooks/use-auth"; import { useAuth } from "@/hooks/use-auth";
import { adminListMenu } from "@/screens/Admin/listPageAdmin"; import {
adminListMenu,
superAdminListMenu,
} from "@/screens/Admin/listPageAdmin";
import { GStyles } from "@/styles/global-styles"; import { GStyles } from "@/styles/global-styles";
import { FontAwesome6, Ionicons } from "@expo/vector-icons"; import { FontAwesome6, Ionicons } from "@expo/vector-icons";
import { router, Stack } from "expo-router"; import { router, Stack } from "expo-router";
@@ -24,8 +27,11 @@ import { useState } from "react";
export default function AdminLayout() { export default function AdminLayout() {
const [openDrawerNavbar, setOpenDrawerNavbar] = useState(false); const [openDrawerNavbar, setOpenDrawerNavbar] = useState(false);
const [openDrawerUser, setOpenDrawerUser] = useState(false); const [openDrawerUser, setOpenDrawerUser] = useState(false);
// const [user, setUser] = useState(null);
const { logout } = useAuth(); const { logout, user } = useAuth();
console.log("[USER LAYOUT]", JSON.stringify(user, null, 2));
return ( return (
<> <>
@@ -56,58 +62,58 @@ export default function AdminLayout() {
), ),
}} }}
> >
<Stack.Screen name="dashboard" /> {/* <Stack.Screen name="dashboard" /> */}
{/* ================== Investment Start ================== */} {/* ================== Investment Start ================== */}
<Stack.Screen name="investment/index" /> {/* <Stack.Screen name="investment/index" /> */}
{/* ================== Investment End ================== */} {/* ================== Investment End ================== */}
{/* ================== Maps Start ================== */} {/* ================== Maps Start ================== */}
<Stack.Screen name="maps" /> {/* <Stack.Screen name="maps" /> */}
{/* ================== Maps End ================== */} {/* ================== Maps End ================== */}
{/* ================== App Information Start ================== */} {/* ================== App Information Start ================== */}
<Stack.Screen name="app-information/index" /> {/* <Stack.Screen name="app-information/index" /> */}
{/* ================== App Information End ================== */} {/* ================== App Information End ================== */}
{/* ================== Job Start ================== */} {/* ================== Job Start ================== */}
<Stack.Screen name="job/index" /> {/* <Stack.Screen name="job/index" /> */}
{/* <Stack.Screen name="job/publish" /> {/* <Stack.Screen name="job/publish" />
<Stack.Screen name="job/review" /> <Stack.Screen name="job/review" />
<Stack.Screen name="job/reject" /> */} <Stack.Screen name="job/reject" /> */}
<Stack.Screen name="job/[status]/status" /> {/* <Stack.Screen name="job/[status]/status" />
<Stack.Screen name="job/[id]/[status]/index" /> <Stack.Screen name="job/[id]/[status]/index" /> */}
{/* ================== Collaboration Start ================== */} {/* ================== Collaboration Start ================== */}
<Stack.Screen name="collaboration/index" /> {/* <Stack.Screen name="collaboration/index" /> */}
<Stack.Screen name="collaboration/publish" /> {/* <Stack.Screen name="collaboration/publish" />
<Stack.Screen name="collaboration/group" /> <Stack.Screen name="collaboration/group" />
<Stack.Screen name="collaboration/reject" /> <Stack.Screen name="collaboration/reject" />
<Stack.Screen name="collaboration/[id]/[status]" /> <Stack.Screen name="collaboration/[id]/[status]" />
<Stack.Screen name="collaboration/[id]/group" /> <Stack.Screen name="collaboration/[id]/group" /> */}
{/* ================== Collaboration End ================== */} {/* ================== Collaboration End ================== */}
{/* ================== Forum Start ================== */} {/* ================== Forum Start ================== */}
<Stack.Screen name="forum/index" /> {/* <Stack.Screen name="forum/index" /> */}
<Stack.Screen name="forum/[id]/index" /> {/* <Stack.Screen name="forum/[id]/index" />
<Stack.Screen name="forum/report-comment" /> <Stack.Screen name="forum/report-comment" />
<Stack.Screen name="forum/report-posting" /> <Stack.Screen name="forum/report-posting" />
<Stack.Screen name="forum/[id]/list-report-posting" /> <Stack.Screen name="forum/[id]/list-report-posting" />
<Stack.Screen name="forum/[id]/list-report-comment" /> <Stack.Screen name="forum/[id]/list-report-comment" /> */}
{/* ================== Forum End ================== */} {/* ================== Forum End ================== */}
{/* ================== Voting Start ================== */} {/* ================== Voting Start ================== */}
<Stack.Screen name="voting/index" /> {/* <Stack.Screen name="voting/index" />
<Stack.Screen name="voting/[status]/status" /> <Stack.Screen name="voting/[status]/status" />
<Stack.Screen name="voting/[id]/[status]/index" /> <Stack.Screen name="voting/[id]/[status]/index" />
<Stack.Screen name="voting/[id]/reject-input" /> <Stack.Screen name="voting/[id]/reject-input" /> */}
{/* ================== Voting End ================== */} {/* ================== Voting End ================== */}
{/* ================== Event Start ================== */} {/* ================== Event Start ================== */}
<Stack.Screen name="event/index" /> {/* <Stack.Screen name="event/index" />
<Stack.Screen name="event/[status]/status" /> <Stack.Screen name="event/[status]/status" />
<Stack.Screen name="event/type-of-event" /> <Stack.Screen name="event/type-of-event" />
<Stack.Screen name="event/type-create" /> <Stack.Screen name="event/type-create" />
<Stack.Screen name="event/type-update" /> <Stack.Screen name="event/type-update" /> */}
{/* <Stack.Screen name="event/[id]/[status]/index" /> {/* <Stack.Screen name="event/[id]/[status]/index" />
<Stack.Screen name="event/[id]/reject-input"/> */} <Stack.Screen name="event/[id]/reject-input"/> */}
{/* ================== Event End ================== */} {/* ================== Event End ================== */}
@@ -134,7 +140,11 @@ export default function AdminLayout() {
/> />
<NavbarMenu <NavbarMenu
items={adminListMenu} items={
user?.masterUserRoleId === "2"
? adminListMenu
: superAdminListMenu
}
onClose={() => setOpenDrawerNavbar(false)} onClose={() => setOpenDrawerNavbar(false)}
/> />
</StackCustom> </StackCustom>
@@ -155,7 +165,7 @@ export default function AdminLayout() {
/> />
} }
> >
<TextCustom>Username</TextCustom> <TextCustom>{user?.username || "-"}</TextCustom>
</GridComponentView> </GridComponentView>
<GridComponentView <GridComponentView
leftIcon={ leftIcon={
@@ -166,7 +176,13 @@ export default function AdminLayout() {
/> />
} }
> >
<TextCustom>User Role</TextCustom> <TextCustom>
{user
? user?.masterUserRoleId === "2"
? "Admin"
: "Super Admin"
: "-"}
</TextCustom>
</GridComponentView> </GridComponentView>
<MenuDrawerDynamicGrid <MenuDrawerDynamicGrid

View File

@@ -1,18 +1,89 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { import {
BoxButtonOnFooter, BoxButtonOnFooter,
ButtonCustom, ButtonCustom,
StackCustom, StackCustom,
TextCustom,
TextInputCustom, TextInputCustom,
ViewWrapper, ViewWrapper,
} from "@/components"; } from "@/components";
import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButtonAntTitle"; import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButtonAntTitle";
import { router } from "expo-router"; import { MainColor } from "@/constants/color-palet";
import {
apiAdminMasterBusinessFieldById,
apiAdminMasterBusinessFieldUpdate,
} from "@/service/api-admin/api-master-admin";
import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
import { useCallback, useState } from "react";
import { Switch } from "react-native-paper";
import Toast from "react-native-toast-message";
export default function AdminAppInformation_BusinessFieldDetail() { export default function AdminAppInformation_BusinessFieldDetail() {
const { id } = useLocalSearchParams();
const [data, setData] = useState<any | null>(null);
const [isLoading, setIsLoading] = useState(false);
useFocusEffect(
useCallback(() => {
onLoadDetail();
}, [id])
);
const onLoadDetail = async () => {
try {
const response = await apiAdminMasterBusinessFieldById({
id: id as string,
});
setData(response.data);
} catch (error) {
console.log("[ERROR]", error);
setData(null);
}
};
const handlerSubmit = async () => {
if (!data.name) {
Toast.show({
type: "error",
text1: "Lengkapi Data",
});
return;
}
try {
setIsLoading(true);
const response = await apiAdminMasterBusinessFieldUpdate({
id: id as string,
data: data,
});
if (!response.success) {
Toast.show({
type: "error",
text1: "Gagal update data",
});
return;
}
Toast.show({
type: "success",
text1: "Data berhasil di update",
});
router.back();
} catch (error) {
console.log(error);
} finally {
setIsLoading(false);
}
};
const buttonSubmit = ( const buttonSubmit = (
<BoxButtonOnFooter> <BoxButtonOnFooter>
<ButtonCustom <ButtonCustom
onPress={() => router.back()} disabled={!data?.name}
isLoading={isLoading}
onPress={() => handlerSubmit()}
> >
Update Update
</ButtonCustom> </ButtonCustom>
@@ -28,6 +99,15 @@ export default function AdminAppInformation_BusinessFieldDetail() {
label="Nama Bidang Bisnis" label="Nama Bidang Bisnis"
placeholder="Masukan Nama Bidang Bisnis" placeholder="Masukan Nama Bidang Bisnis"
required required
value={data?.name}
onChangeText={(value) => setData({ ...data, name: value })}
/>
<TextCustom>Status Aktivasi</TextCustom>
<Switch
color={MainColor.yellow}
value={data?.active}
onValueChange={(value) => setData({ ...data, active: value })}
/> />
</StackCustom> </StackCustom>
</ViewWrapper> </ViewWrapper>

View File

@@ -6,13 +6,52 @@ import {
ViewWrapper, ViewWrapper,
} from "@/components"; } from "@/components";
import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButtonAntTitle"; import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButtonAntTitle";
import { apiAdminMasterBusinessFieldCreate } from "@/service/api-admin/api-master-admin";
import { router } from "expo-router"; import { router } from "expo-router";
import { useState } from "react";
import Toast from "react-native-toast-message";
export default function AdminAppInformation_BusinessFieldCreate() { export default function AdminAppInformation_BusinessFieldCreate() {
const [data, setData] = useState<any>({
name: "",
});
const [isLoading, setIsLoading] = useState(false);
const handlerSubmit = async () => {
if (!data.name) {
Toast.show({
type: "error",
text1: "Lengkapi Data",
});
return;
}
try {
setIsLoading(true);
const response = await apiAdminMasterBusinessFieldCreate({ data: data });
if (response.success) {
Toast.show({
type: "success",
text1: "Data berhasil di tambah",
});
router.back();
}
} catch (error) {
console.log("[ERROR]", error);
Toast.show({
type: "error",
text1: "Gagal tambah data",
});
} finally {
setIsLoading(false);
}
};
const buttonSubmit = ( const buttonSubmit = (
<BoxButtonOnFooter> <BoxButtonOnFooter>
<ButtonCustom <ButtonCustom
onPress={() => router.back()} onPress={() => handlerSubmit()}
isLoading={isLoading}
> >
Tambah Tambah
</ButtonCustom> </ButtonCustom>
@@ -28,6 +67,8 @@ export default function AdminAppInformation_BusinessFieldCreate() {
label="Nama Bidang Bisnis" label="Nama Bidang Bisnis"
placeholder="Masukan Nama Bidang Bisnis" placeholder="Masukan Nama Bidang Bisnis"
required required
value={data.name}
onChangeText={(value) => setData({ ...data, name: value })}
/> />
</StackCustom> </StackCustom>
</ViewWrapper> </ViewWrapper>

View File

@@ -1,8 +1,4 @@
import { import { ScrollableCustom, StackCustom, ViewWrapper } from "@/components";
ScrollableCustom,
StackCustom,
ViewWrapper
} from "@/components";
import AdminActionIconPlus from "@/components/_ShareComponent/Admin/ActionIconPlus"; import AdminActionIconPlus from "@/components/_ShareComponent/Admin/ActionIconPlus";
import AdminComp_BoxTitle from "@/components/_ShareComponent/Admin/BoxTitlePage"; import AdminComp_BoxTitle from "@/components/_ShareComponent/Admin/BoxTitlePage";
import AdminAppInformation_BusinessFieldSection from "@/screens/Admin/App-Information/BusinessFieldSection"; import AdminAppInformation_BusinessFieldSection from "@/screens/Admin/App-Information/BusinessFieldSection";
@@ -10,6 +6,7 @@ import AdminAppInformation_Bank from "@/screens/Admin/App-Information/Informatio
import AdminAppInformation_StickerSection from "@/screens/Admin/App-Information/StickerSection"; import AdminAppInformation_StickerSection from "@/screens/Admin/App-Information/StickerSection";
import { router } from "expo-router"; import { router } from "expo-router";
import { useState } from "react"; import { useState } from "react";
import { Alert } from "react-native";
export default function AdminInformation() { export default function AdminInformation() {
const [activeCategory, setActiveCategory] = useState<string | null>("bank"); const [activeCategory, setActiveCategory] = useState<string | null>("bank");
@@ -57,7 +54,8 @@ export default function AdminInformation() {
} else if (activeCategory === "business") { } else if (activeCategory === "business") {
router.push("/admin/app-information/business-field/create"); router.push("/admin/app-information/business-field/create");
} else if (activeCategory === "sticker") { } else if (activeCategory === "sticker") {
router.push("/admin/app-information/sticker/create"); Alert.alert("Coming Soon", "Next Update");
// router.push("/admin/app-information/sticker/create");
} }
}} }}
/> />

View File

@@ -1,18 +1,90 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { import {
BoxButtonOnFooter, BoxButtonOnFooter,
ButtonCustom, ButtonCustom,
StackCustom, CenterCustom,
TextInputCustom, Grid,
ViewWrapper, StackCustom,
TextCustom,
TextInputCustom,
ViewWrapper,
} from "@/components"; } from "@/components";
import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButtonAntTitle"; import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButtonAntTitle";
import { router } from "expo-router"; import { MainColor } from "@/constants/color-palet";
import {
apiAdminMasterBankById,
apiAdminMasterBankUpdate,
} from "@/service/api-admin/api-master-admin";
import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
import { useCallback, useState } from "react";
import { Switch } from "react-native-paper";
import Toast from "react-native-toast-message";
export default function AdminAppInformation_BankDetail() { export default function AdminAppInformation_BankDetail() {
const { id } = useLocalSearchParams();
const [data, setData] = useState<any | null>(null);
const [isLoading, setLoading] = useState(false);
useFocusEffect(
useCallback(() => {
onLoadList();
}, [id])
);
const onLoadList = async () => {
try {
const response = await apiAdminMasterBankById({ id: id as string });
if (response.success) {
setData(response.data);
}
} catch (error) {
console.log("[ERROR]", error);
}
};
const handlerUpdate = async () => {
if (!data.namaBank || !data.namaAkun || !data.norek) {
Toast.show({
type: "error",
text1: "Lengkapi Data",
});
return;
}
try {
setLoading(true);
const response = await apiAdminMasterBankUpdate({
id: id as string,
data: data,
});
if (!response.success) {
Toast.show({
type: "error",
text1: "Gagal update data",
});
} else {
Toast.show({
type: "success",
text1: "Success",
text2: "Data berhasil di update",
});
router.back();
}
} catch (error) {
console.log("[ERROR]", error);
} finally {
setLoading(false);
}
};
const buttonSubmit = ( const buttonSubmit = (
<BoxButtonOnFooter> <BoxButtonOnFooter>
<ButtonCustom <ButtonCustom
onPress={() => router.back()} disabled={!data?.namaBank || !data?.namaAkun || !data?.norek}
isLoading={isLoading}
onPress={() => handlerUpdate()}
> >
Update Update
</ButtonCustom> </ButtonCustom>
@@ -25,22 +97,46 @@ export default function AdminAppInformation_BankDetail() {
<AdminBackButtonAntTitle title="Update Bank" /> <AdminBackButtonAntTitle title="Update Bank" />
<StackCustom> <StackCustom>
<TextInputCustom <Grid>
label="Nama Bank" <Grid.Col span={6}>
placeholder="Masukan Nama Bank" <TextInputCustom
required label="Nama Bank"
/> placeholder="Masukan Nama Bank"
required
value={data?.namaBank}
onChangeText={(value) =>
setData({ ...data, namaBank: value })
}
/>
</Grid.Col>
<Grid.Col span={6} style={{ alignItems: "center" }}>
<TextCustom>Status Aktivasi</TextCustom>
<CenterCustom>
<Switch
onValueChange={(value) =>
setData({ ...data, isActive: value })
}
color={MainColor.yellow}
value={data?.isActive}
/>
</CenterCustom>
</Grid.Col>
</Grid>
<TextInputCustom <TextInputCustom
label="Nama Rekening" label="Nama Rekening"
placeholder="Masukan Nama Rekening" placeholder="Masukan Nama Rekening"
required required
value={data?.namaAkun}
onChangeText={(value) => setData({ ...data, namaAkun: value })}
/> />
<TextInputCustom <TextInputCustom
label="Nomor Rekening" label="Nomor Rekening"
placeholder="Masukan Nomor Rekening" placeholder="Masukan Nomor Rekening"
required required
value={data?.norek}
onChangeText={(value) => setData({ ...data, norek: value })}
/> />
</StackCustom> </StackCustom>
</StackCustom> </StackCustom>

View File

@@ -1,19 +1,51 @@
import { import {
BoxButtonOnFooter, BoxButtonOnFooter,
ButtonCustom, ButtonCustom,
StackCustom, StackCustom,
TextInputCustom, TextInputCustom,
ViewWrapper ViewWrapper,
} from "@/components"; } from "@/components";
import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButtonAntTitle"; import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButtonAntTitle";
import { apiAdminMasterBankCreate } from "@/service/api-admin/api-master-admin";
import { router } from "expo-router"; import { router } from "expo-router";
import { useState } from "react";
import Toast from "react-native-toast-message";
export default function AdminAppInformation_BankCreate() { export default function AdminAppInformation_BankCreate() {
const [data, setData] = useState<any>({
namaBank: "",
namaAkun: "",
norek: "",
});
const [isLoading, setLoading] = useState(false);
const handlerSubmit = async () => {
try {
setLoading(true);
const response = await apiAdminMasterBankCreate({ data: data });
if (response.success) {
Toast.show({
type: "success",
text1: "Data berhasil di tambah",
});
router.back();
}
} catch (error) {
console.log("[ERROR]", error);
Toast.show({
type: "error",
text1: "Gagal tambah data",
});
} finally {
setLoading(false);
}
};
const buttonSubmit = ( const buttonSubmit = (
<BoxButtonOnFooter> <BoxButtonOnFooter>
<ButtonCustom <ButtonCustom isLoading={isLoading} onPress={handlerSubmit}>
onPress={() => router.back()}
>
Tambah Tambah
</ButtonCustom> </ButtonCustom>
</BoxButtonOnFooter> </BoxButtonOnFooter>
@@ -29,18 +61,25 @@ export default function AdminAppInformation_BankCreate() {
label="Nama Bank" label="Nama Bank"
placeholder="Masukan Nama Bank" placeholder="Masukan Nama Bank"
required required
value={data.namaBank}
onChangeText={(value) => setData({ ...data, namaBank: value })}
/> />
<TextInputCustom <TextInputCustom
label="Nama Rekening" label="Nama Rekening"
placeholder="Masukan Nama Rekening" placeholder="Masukan Nama Rekening"
required required
value={data.namaAkun}
onChangeText={(value) => setData({ ...data, namaAkun: value })}
/> />
<TextInputCustom <TextInputCustom
keyboardType="numeric"
label="Nomor Rekening" label="Nomor Rekening"
placeholder="Masukan Nomor Rekening" placeholder="Masukan Nomor Rekening"
required required
value={data.norek}
onChangeText={(value) => setData({ ...data, norek: value })}
/> />
</StackCustom> </StackCustom>
</StackCustom> </StackCustom>

View File

@@ -1,3 +1,4 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { import {
BaseBox, BaseBox,
BoxButtonOnFooter, BoxButtonOnFooter,
@@ -8,18 +9,45 @@ import {
ViewWrapper, ViewWrapper,
} from "@/components"; } from "@/components";
import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButtonAntTitle"; import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButtonAntTitle";
import GridTwoView from "@/components/_ShareComponent/GridTwoView";
import { MainColor } from "@/constants/color-palet"; import { MainColor } from "@/constants/color-palet";
import { useLocalSearchParams } from "expo-router"; import { apiAdminCollaborationGetById } from "@/service/api-admin/api-admin-collaboration";
import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
import { useCallback, useState } from "react";
export default function AdminCollaborationPublish() { export default function AdminCollaborationPublish() {
const { id, status } = useLocalSearchParams(); const { id, status } = useLocalSearchParams();
console.log("params:", id, status); const [data, setData] = useState<any | null>(null);
const bottomFooter = (
useFocusEffect(
useCallback(() => {
handlerLoadData();
}, [status])
);
const handlerLoadData = async () => {
try {
const response = await apiAdminCollaborationGetById({
id: id as string,
category: status as any,
});
if (response.success) {
setData(response.data);
}
} catch (error) {
console.log("[ERROR]", error);
}
};
const bottomFooter = status === "publish" && (
<BoxButtonOnFooter> <BoxButtonOnFooter>
<ButtonCustom <ButtonCustom
backgroundColor={MainColor.red} backgroundColor={MainColor.red}
textColor="white" textColor="white"
onPress={() => {}} onPress={() => {
router.push(`/admin/collaboration/${id}/reject-input`);
}}
> >
Reject Reject
</ButtonCustom> </ButtonCustom>
@@ -29,12 +57,12 @@ export default function AdminCollaborationPublish() {
return ( return (
<> <>
<ViewWrapper <ViewWrapper
headerComponent={<AdminBackButtonAntTitle title={`Detail ${status}`} />} headerComponent={<AdminBackButtonAntTitle title={`Detail`} />}
footerComponent={bottomFooter} footerComponent={bottomFooter}
> >
<BaseBox> <BaseBox>
<StackCustom> <StackCustom>
{listData.map((item, i) => ( {listData(data)?.map((item, i) => (
<Grid key={i}> <Grid key={i}>
<Grid.Col <Grid.Col
span={4} span={4}
@@ -49,41 +77,49 @@ export default function AdminCollaborationPublish() {
))} ))}
</StackCustom> </StackCustom>
</BaseBox> </BaseBox>
{data?.report && (
<BaseBox>
<GridTwoView
spanLeft={4}
spanRight={8}
leftIcon={<TextCustom bold>Catatan report</TextCustom>}
rightIcon={<TextCustom>{data?.report}</TextCustom>}
/>
</BaseBox>
)}
</ViewWrapper> </ViewWrapper>
</> </>
); );
} }
const listData = [ const listData = (data: any) => [
{ {
label: "Username", label: "Username",
value: "Bagas Banuna", value: (data && data?.Author?.username) || "-",
}, },
{ {
label: "Judul Proyek", label: "Judul Proyek",
value: value: (data && data?.title) || "-",
"Judul Proyek: Lorem ipsum dolor sit amet consectetur adipisicing elit.",
}, },
{ {
label: "Industri", label: "Industri",
value: "Kesehatan", value: (data && data?.ProjectCollaborationMaster_Industri?.name) || "-",
}, },
{ {
label: "Jumlah Partisipan ", label: "Jumlah Partisipan ",
value: "0", value: (data && data?.ProjectCollaboration_Partisipasi.length) || "0",
}, },
{ {
label: "Lokasi", label: "Lokasi",
value: "Kuta Selatan, Bali", value: (data && data?.lokasi) || "-",
}, },
{ {
label: "Tujuan Proyek", label: "Tujuan Proyek",
value: value: (data && data?.purpose) || "-",
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
}, },
{ {
label: "Keuntungan", label: "Keuntungan",
value: value: (data && data?.benefit) || "-",
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
}, },
]; ];

View File

@@ -1,3 +1,4 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { import {
BaseBox, BaseBox,
Grid, Grid,
@@ -6,23 +7,45 @@ import {
ViewWrapper, ViewWrapper,
} from "@/components"; } from "@/components";
import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButtonAntTitle"; import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButtonAntTitle";
import { useLocalSearchParams } from "expo-router"; import { apiAdminCollaborationGetById } from "@/service/api-admin/api-admin-collaboration";
import { useFocusEffect, useLocalSearchParams } from "expo-router";
import { useCallback, useState } from "react";
export default function AdminCollaborationGroup() { export default function AdminCollaborationGroup() {
const { id } = useLocalSearchParams(); const { id } = useLocalSearchParams();
console.log("params:", id); const [data, setData] = useState<any | null>(null);
useFocusEffect(
useCallback(() => {
onLoadData();
}, [id])
);
const onLoadData = async () => {
try {
const response = await apiAdminCollaborationGetById({
id: id as string,
category: "group",
});
if (response.success) {
setData(response.data);
}
} catch (error) {
console.log("[ERROR]", error);
}
};
return ( return (
<> <>
<ViewWrapper <ViewWrapper
headerComponent={ headerComponent={<AdminBackButtonAntTitle title={`Detail Group`} />}
<AdminBackButtonAntTitle title={`Detail Group ${id}`} />
}
> >
<StackCustom gap={"xs"}> <StackCustom gap={"xs"}>
<BaseBox> <BaseBox>
<StackCustom> <StackCustom>
{listData.map((item, i) => ( {listData(data).map((item: any, index: number) => (
<Grid key={i}> <Grid key={index}>
<Grid.Col <Grid.Col
span={4} span={4}
style={{ justifyContent: "center", paddingRight: 10 }} style={{ justifyContent: "center", paddingRight: 10 }}
@@ -40,21 +63,36 @@ export default function AdminCollaborationGroup() {
<StackCustom> <StackCustom>
<TextCustom align="center">Anggota</TextCustom> <TextCustom align="center">Anggota</TextCustom>
{Array.from({ length: 10 }).map((_, i) => ( {data?.ProjectCollaboration_AnggotaRoomChat?.map(
<Grid key={i}> (item: any, index: number) => (
<Grid.Col <StackCustom key={index} gap={0}>
span={4} <Grid>
style={{ justifyContent: "center", paddingRight: 10 }} <Grid.Col
> span={4}
<TextCustom bold>Username</TextCustom> style={{ justifyContent: "center", paddingRight: 10 }}
</Grid.Col> >
<Grid.Col span={8} style={{ justifyContent: "center" }}> <TextCustom bold>Nama</TextCustom>
<TextCustom> </Grid.Col>
Lorem ipsum dolor sit amet consectetur adipisicing elit. <Grid.Col span={8} style={{ justifyContent: "center" }}>
</TextCustom> <TextCustom>
</Grid.Col> {item?.User?.Profile?.name || "-"}
</Grid> </TextCustom>
))} </Grid.Col>
</Grid>
<Grid>
<Grid.Col
span={4}
style={{ justifyContent: "center", paddingRight: 10 }}
>
<TextCustom bold>Username</TextCustom>
</Grid.Col>
<Grid.Col span={8} style={{ justifyContent: "center" }}>
<TextCustom>{item?.User?.username || "-"}</TextCustom>
</Grid.Col>
</Grid>
</StackCustom>
)
)}
</StackCustom> </StackCustom>
</BaseBox> </BaseBox>
</StackCustom> </StackCustom>
@@ -63,35 +101,35 @@ export default function AdminCollaborationGroup() {
); );
} }
const listData = [ const listData = (data: any) => [
{ {
label: "Admin Group", label: "Admin Group",
value: "Bagas Banuna", value: data?.ProjectCollaboration?.Author?.username || "-",
}, },
{ {
label: "Nama Group", label: "Nama Group",
value: "Lorem ipsum dolor sit amet consectetur adipisicing elit.", value: data?.name || "-",
}, },
{ {
label: "Industri", label: "Industri",
value: "Kesehatan", value:
data?.ProjectCollaboration?.ProjectCollaborationMaster_Industri?.name ||
"-",
}, },
{ {
label: "Jumlah Partisipan ", label: "Jumlah Partisipan ",
value: "0", value: data?.ProjectCollaboration_AnggotaRoomChat?.length || "-",
}, },
{ {
label: "Lokasi", label: "Lokasi",
value: "Kuta Selatan, Bali", value: data?.ProjectCollaboration?.lokasi || "-",
}, },
{ {
label: "Tujuan Proyek", label: "Tujuan Proyek",
value: value: data?.ProjectCollaboration?.purpose || "-",
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
}, },
{ {
label: "Keuntungan", label: "Keuntungan",
value: value: data?.ProjectCollaboration?.benefit || "-",
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
}, },
]; ];

View File

@@ -6,12 +6,42 @@ import {
} from "@/components"; } from "@/components";
import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButtonAntTitle"; import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButtonAntTitle";
import AdminButtonReject from "@/components/_ShareComponent/Admin/ButtonReject"; import AdminButtonReject from "@/components/_ShareComponent/Admin/ButtonReject";
import { apiAdminCollaborationReject } from "@/service/api-admin/api-admin-collaboration";
import { router, useLocalSearchParams } from "expo-router"; import { router, useLocalSearchParams } from "expo-router";
import { useState } from "react"; import { useState } from "react";
import Toast from "react-native-toast-message";
export default function AdminVotingRejectInput() { export default function AdminCollaborationRejectInput() {
const { id } = useLocalSearchParams(); const { id } = useLocalSearchParams();
const [value, setValue] = useState(id as string); const [value, setValue] = useState("");
const handlerReject = async () => {
console.log("value:", value);
// router.replace(`/admin/job/reject/status`);
try {
const response = await apiAdminCollaborationReject({
id: id as string,
data: value,
});
if (!response.success) {
Toast.show({
type: "error",
text1: "Report gagal",
});
}
Toast.show({
type: "success",
text1: "Report berhasil",
});
router.back();
} catch (error) {
console.log("[ERROR]", error);
}
};
const buttonSubmit = ( const buttonSubmit = (
<BoxButtonOnFooter> <BoxButtonOnFooter>
<AdminButtonReject <AdminButtonReject
@@ -22,12 +52,8 @@ export default function AdminVotingRejectInput() {
message: "Apakah anda yakin ingin menolak data ini?", message: "Apakah anda yakin ingin menolak data ini?",
textLeft: "Batal", textLeft: "Batal",
textRight: "Ya", textRight: "Ya",
onPressLeft: () => {
router.back();
},
onPressRight: () => { onPressRight: () => {
console.log("value:", value); handlerReject();
router.replace(`/admin/voting/reject/status`);
}, },
}) })
} }
@@ -39,7 +65,9 @@ export default function AdminVotingRejectInput() {
<> <>
<ViewWrapper <ViewWrapper
footerComponent={buttonSubmit} footerComponent={buttonSubmit}
headerComponent={<AdminBackButtonAntTitle title="Penolakan Voting" />} headerComponent={
<AdminBackButtonAntTitle title="Penolakan Collaboration" />
}
> >
<TextAreaCustom <TextAreaCustom
value={value} value={value}

View File

@@ -1,67 +1,101 @@
import { import {
ActionIcon, ActionIcon,
BaseBox, LoaderCustom,
Spacing,
StackCustom, StackCustom,
TextCustom, TextCustom,
ViewWrapper, ViewWrapper
} from "@/components"; } from "@/components";
import AdminComp_BoxTitle from "@/components/_ShareComponent/Admin/BoxTitlePage"; import AdminComp_BoxTitle from "@/components/_ShareComponent/Admin/BoxTitlePage";
import AdminTitleTable from "@/components/_ShareComponent/Admin/TableTitle"; import AdminTitleTable from "@/components/_ShareComponent/Admin/TableTitle";
import AdminTableValue from "@/components/_ShareComponent/Admin/TableValue"; import AdminTableValue from "@/components/_ShareComponent/Admin/TableValue";
import AdminTitlePage from "@/components/_ShareComponent/Admin/TitlePage"; import AdminTitlePage from "@/components/_ShareComponent/Admin/TitlePage";
import { ICON_SIZE_BUTTON } from "@/constants/constans-value"; import { ICON_SIZE_BUTTON } from "@/constants/constans-value";
import { apiAdminCollaboration } from "@/service/api-admin/api-admin-collaboration";
import { Octicons } from "@expo/vector-icons"; import { Octicons } from "@expo/vector-icons";
import { router } from "expo-router"; import { router, useFocusEffect } from "expo-router";
import _ from "lodash";
import { useCallback, useState } from "react";
import { View } from "react-native";
import { Divider } from "react-native-paper"; import { Divider } from "react-native-paper";
export default function AdminCollaborationGroup() { export default function AdminCollaborationGroup() {
const [list, setList] = useState<any[] | null>(null);
const [loadList, setLoadList] = useState(false);
useFocusEffect(
useCallback(() => {
handlerLoadList();
}, [])
);
const handlerLoadList = async () => {
try {
setLoadList(true);
const response = await apiAdminCollaboration({
category: "group",
});
if (response.success) {
setList(response.data);
}
} catch (error) {
console.log("[ERROR]", error);
} finally {
setLoadList(false);
}
};
return ( return (
<> <>
<ViewWrapper headerComponent={<AdminTitlePage title="Collaboration" />}> <ViewWrapper headerComponent={<AdminTitlePage title="Collaboration" />}>
<StackCustom gap={"xs"}> <StackCustom>
<AdminComp_BoxTitle title="Group" /> <AdminComp_BoxTitle title="Group" />
<BaseBox> <>
<AdminTitleTable <AdminTitleTable
title1="Aksi" title1="Aksi"
title2="Admin Group" title2="Jumlah peserta"
title3="Nama Group" title3="Nama group"
/> />
<Spacing height={10} />
<Divider /> <Divider />
{Array.from({ length: 10 }).map((_, index) => ( {loadList ? (
<AdminTableValue <LoaderCustom />
key={index} ) : _.isEmpty(list) ? (
value1={ <TextCustom align="center" color="gray">
<ActionIcon Belum ada data
icon={ </TextCustom>
<Octicons ) : (
name="eye" list?.map((item: any, index: number) => (
size={ICON_SIZE_BUTTON} <View key={index}>
color="black" <AdminTableValue
value1={
<ActionIcon
icon={
<Octicons
name="eye"
size={ICON_SIZE_BUTTON}
color="black"
/>
}
onPress={() => {
router.push(`/admin/collaboration/${item.id}/group`);
}}
/> />
} }
onPress={() => { value2={
router.push(`/admin/collaboration/${index}/group`); <TextCustom truncate={1}>
}} {item?.ProjectCollaboration_AnggotaRoomChat?.length ||
"-"}
</TextCustom>
}
value3={
<TextCustom truncate={2}>{item?.name || "-"}</TextCustom>
}
/> />
} </View>
value2={ ))
<TextCustom truncate={1}>Username username </TextCustom> )}
} </>
value3={
<TextCustom truncate={2}>
Lorem ipsum dolor sit amet consectetur adipisicing elit.
Blanditiis asperiores quidem deleniti architecto eaque et
nostrum, ad consequuntur eveniet quisquam quae voluptatum
ducimus! Dolorem nobis modi officia debitis, beatae
mollitia.
</TextCustom>
}
/>
))}
</BaseBox>
</StackCustom> </StackCustom>
</ViewWrapper> </ViewWrapper>
</> </>

View File

@@ -2,14 +2,39 @@ import { StackCustom, ViewWrapper } from "@/components";
import AdminComp_BoxDashboard from "@/components/_ShareComponent/Admin/BoxDashboard"; import AdminComp_BoxDashboard from "@/components/_ShareComponent/Admin/BoxDashboard";
import AdminTitlePage from "@/components/_ShareComponent/Admin/TitlePage"; import AdminTitlePage from "@/components/_ShareComponent/Admin/TitlePage";
import { MainColor } from "@/constants/color-palet"; import { MainColor } from "@/constants/color-palet";
import { apiAdminCollaboration } from "@/service/api-admin/api-admin-collaboration";
import { Entypo, FontAwesome } from "@expo/vector-icons"; import { Entypo, FontAwesome } from "@expo/vector-icons";
import { useFocusEffect } from "expo-router";
import { useCallback, useState } from "react";
export default function AdminCollaboration() { export default function AdminCollaboration() {
const [list, setList] = useState<any | null>(null);
useFocusEffect(
useCallback(() => {
handlerLoadList();
}, [])
);
const handlerLoadList = async () => {
try {
const response = await apiAdminCollaboration({
category: "dashboard",
});
if (response.success) {
setList(response.data);
}
} catch (error) {
console.log("[ERROR]", error);
}
};
return ( return (
<> <>
<ViewWrapper headerComponent={<AdminTitlePage title="Collaboration" />}> <ViewWrapper headerComponent={<AdminTitlePage title="Collaboration" />}>
<StackCustom gap={"xs"}> <StackCustom gap={"xs"}>
{listData.map((item, i) => ( {listData(list as any).map((item, i) => (
<AdminComp_BoxDashboard key={i} item={item} /> <AdminComp_BoxDashboard key={i} item={item} />
))} ))}
</StackCustom> </StackCustom>
@@ -18,20 +43,22 @@ export default function AdminCollaboration() {
); );
} }
const listData = [ const listData = (list: any) => {
{ return [
label: "Publish", {
value: 4, label: "Publish",
icon: <Entypo name="publish" size={25} color={MainColor.green} />, value: (list && list?.publish) || "0",
}, icon: <Entypo name="publish" size={25} color={MainColor.green} />,
{ },
label: "Group", {
value: 7, label: "Group",
icon: <FontAwesome name="group" size={25} color={MainColor.yellow} />, value: (list && list?.group) || "0",
}, icon: <FontAwesome name="group" size={25} color={MainColor.yellow} />,
{ },
label: "Reject", {
value: 7, label: "Reject",
icon: <FontAwesome name="warning" size={25} color={MainColor.red} />, value: (list && list?.reject) || "0",
}, icon: <FontAwesome name="warning" size={25} color={MainColor.red} />,
]; },
];
};

View File

@@ -1,7 +1,6 @@
import { import {
ActionIcon, ActionIcon,
BaseBox, LoaderCustom,
Spacing,
StackCustom, StackCustom,
TextCustom, TextCustom,
ViewWrapper, ViewWrapper,
@@ -11,60 +10,95 @@ import AdminTitleTable from "@/components/_ShareComponent/Admin/TableTitle";
import AdminTableValue from "@/components/_ShareComponent/Admin/TableValue"; import AdminTableValue from "@/components/_ShareComponent/Admin/TableValue";
import AdminTitlePage from "@/components/_ShareComponent/Admin/TitlePage"; import AdminTitlePage from "@/components/_ShareComponent/Admin/TitlePage";
import { ICON_SIZE_BUTTON } from "@/constants/constans-value"; import { ICON_SIZE_BUTTON } from "@/constants/constans-value";
import { apiAdminCollaboration } from "@/service/api-admin/api-admin-collaboration";
import { Octicons } from "@expo/vector-icons"; import { Octicons } from "@expo/vector-icons";
import { router } from "expo-router"; import { router, useFocusEffect } from "expo-router";
import _ from "lodash";
import { useCallback, useState } from "react";
import { View } from "react-native";
import { Divider } from "react-native-paper"; import { Divider } from "react-native-paper";
export default function AdminCollaborationPublish() { export default function AdminCollaborationPublish() {
const [list, setList] = useState<any[] | null>(null);
const [loadList, setLoadList] = useState(false);
useFocusEffect(
useCallback(() => {
handlerLoadList();
}, [])
);
const handlerLoadList = async () => {
try {
setLoadList(true);
const response = await apiAdminCollaboration({
category: "publish",
});
if (response.success) {
setList(response.data);
}
} catch (error) {
console.log("[ERROR]", error);
} finally {
setLoadList(false);
}
};
return ( return (
<> <>
<ViewWrapper headerComponent={<AdminTitlePage title="Collaboration" />}> <ViewWrapper headerComponent={<AdminTitlePage title="Collaboration" />}>
<StackCustom gap={"xs"}> <StackCustom>
<AdminComp_BoxTitle title="Publish" /> <AdminComp_BoxTitle title="Publish" />
<BaseBox>
<AdminTitleTable
title1="Aksi"
title2="Username"
title3="Judul Proyek"
/>
<Spacing height={10} />
<Divider />
{Array.from({ length: 10 }).map((_, index) => ( <AdminTitleTable
<AdminTableValue title1="Aksi"
key={index} title2="Username"
value1={ title3="Judul Proyek"
<ActionIcon />
icon={ {/* <Spacing height={10} /> */}
<Octicons <Divider />
name="eye"
size={ICON_SIZE_BUTTON} {loadList ? (
color="black" <LoaderCustom />
/> ) : _.isEmpty(list) ? (
} <TextCustom align="center" color="gray">
onPress={() => { Belum ada data
router.push(`/admin/collaboration/${index}/publish`); </TextCustom>
}} ) : (
/> list?.map((item: any, index: number) => (
} <View key={index}>
value2={ <AdminTableValue
<TextCustom truncate={1}>Username username </TextCustom> value1={
} <ActionIcon
value3={ icon={
<TextCustom truncate={2}> <Octicons
Lorem ipsum dolor sit amet consectetur adipisicing elit. name="eye"
Blanditiis asperiores quidem deleniti architecto eaque et size={ICON_SIZE_BUTTON}
nostrum, ad consequuntur eveniet quisquam quae voluptatum color="black"
ducimus! Dolorem nobis modi officia debitis, beatae />
mollitia. }
</TextCustom> onPress={() => {
} router.push(`/admin/collaboration/${item?.id}/publish`);
/> }}
))} />
</BaseBox> }
value2={
<TextCustom align="center" truncate={1}>
{item?.Author?.username || "-"}{" "}
</TextCustom>
}
value3={
<TextCustom align="center" truncate={2}>
{item?.title || "-"}
</TextCustom>
}
/>
</View>
))
)}
</StackCustom> </StackCustom>
</ViewWrapper> </ViewWrapper>
</> </>
); );
} }

View File

@@ -1,67 +1,99 @@
import { import {
ActionIcon, ActionIcon,
BaseBox, LoaderCustom,
Spacing,
StackCustom, StackCustom,
TextCustom, TextCustom,
ViewWrapper, ViewWrapper
} from "@/components"; } from "@/components";
import AdminComp_BoxTitle from "@/components/_ShareComponent/Admin/BoxTitlePage"; import AdminComp_BoxTitle from "@/components/_ShareComponent/Admin/BoxTitlePage";
import AdminTitleTable from "@/components/_ShareComponent/Admin/TableTitle"; import AdminTitleTable from "@/components/_ShareComponent/Admin/TableTitle";
import AdminTableValue from "@/components/_ShareComponent/Admin/TableValue"; import AdminTableValue from "@/components/_ShareComponent/Admin/TableValue";
import AdminTitlePage from "@/components/_ShareComponent/Admin/TitlePage"; import AdminTitlePage from "@/components/_ShareComponent/Admin/TitlePage";
import { ICON_SIZE_BUTTON } from "@/constants/constans-value"; import { ICON_SIZE_BUTTON } from "@/constants/constans-value";
import { apiAdminCollaboration } from "@/service/api-admin/api-admin-collaboration";
import { Octicons } from "@expo/vector-icons"; import { Octicons } from "@expo/vector-icons";
import { router } from "expo-router"; import { router, useFocusEffect } from "expo-router";
import _ from "lodash";
import { useCallback, useState } from "react";
import { View } from "react-native";
import { Divider } from "react-native-paper"; import { Divider } from "react-native-paper";
export default function AdminCollaborationReject() { export default function AdminCollaborationReject() {
const [list, setList] = useState<any[] | null>(null);
const [loadList, setLoadList] = useState(false);
useFocusEffect(
useCallback(() => {
handlerLoadList();
}, [])
);
const handlerLoadList = async () => {
try {
setLoadList(true);
const response = await apiAdminCollaboration({
category: "reject",
});
if (response.success) {
setList(response.data);
}
} catch (error) {
console.log("[ERROR]", error);
} finally {
setLoadList(false);
}
};
return ( return (
<> <>
<ViewWrapper headerComponent={<AdminTitlePage title="Collaboration" />}> <ViewWrapper headerComponent={<AdminTitlePage title="Collaboration" />}>
<StackCustom gap={"xs"}> <StackCustom>
<AdminComp_BoxTitle title="Reject" /> <AdminComp_BoxTitle title="Reject" />
<BaseBox> <AdminTitleTable
<AdminTitleTable title1="Aksi"
title1="Aksi" title2="Username"
title2="Username" title3="Judul Proyek"
title3="Judul Proyek" />
/> <Divider />
<Spacing height={10} />
<Divider />
{Array.from({ length: 10 }).map((_, index) => ( {loadList ? (
<AdminTableValue <LoaderCustom />
key={index} ) : _.isEmpty(list) ? (
value1={ <TextCustom>Belum ada data</TextCustom>
<ActionIcon ) : (
icon={ list?.map((item: any) => (
<Octicons <View key={item.id}>
name="eye" <AdminTableValue
size={ICON_SIZE_BUTTON} key={item?.id}
color="black" value1={
/> <ActionIcon
} icon={
onPress={() => { <Octicons
router.push(`/admin/collaboration/${index}/reject`); name="eye"
}} size={ICON_SIZE_BUTTON}
/> color="black"
} />
value2={ }
<TextCustom truncate={1}>Username username </TextCustom> onPress={() => {
} router.push(`/admin/collaboration/${item?.id}/reject`);
value3={ }}
<TextCustom truncate={2}> />
Lorem ipsum dolor sit amet consectetur adipisicing elit. }
Blanditiis asperiores quidem deleniti architecto eaque et value2={
nostrum, ad consequuntur eveniet quisquam quae voluptatum <TextCustom align="center" truncate={1}>
ducimus! Dolorem nobis modi officia debitis, beatae {item?.Author?.username || "-"}
mollitia. </TextCustom>
</TextCustom> }
} value3={
/> <TextCustom align="center" truncate={2}>
))} {item?.title || "-"}
</BaseBox> </TextCustom>
}
/>
</View>
))
)}
</StackCustom> </StackCustom>
</ViewWrapper> </ViewWrapper>
</> </>

View File

@@ -1,13 +1,31 @@
import { import { StackCustom, TextCustom, ViewWrapper } from "@/components";
StackCustom,
TextCustom,
ViewWrapper
} from "@/components";
import AdminComp_BoxDashboard from "@/components/_ShareComponent/Admin/BoxDashboard"; import AdminComp_BoxDashboard from "@/components/_ShareComponent/Admin/BoxDashboard";
import { MainColor } from "@/constants/color-palet"; import { MainColor } from "@/constants/color-palet";
import { apiAdminMainDashboardGetAll } from "@/service/api-admin/api-admin-main-dashboard";
import { Ionicons } from "@expo/vector-icons"; import { Ionicons } from "@expo/vector-icons";
import { useEffect, useState } from "react";
export default function AdminDashboard() { export default function AdminDashboard() {
const [countUser, setCountUser] = useState(0);
const [countPortofolio, setCountPortofolio] = useState(0);
useEffect(() => {
onLoadData();
}, []);
const onLoadData = async () => {
try {
const response = await apiAdminMainDashboardGetAll();
if (response.success) {
setCountUser(response.data.user);
setCountPortofolio(response.data.portofolio);
}
} catch (error) {
console.log("[ERROR LOAD DATA]", error);
}
};
return ( return (
<> <>
<ViewWrapper> <ViewWrapper>
@@ -15,7 +33,7 @@ export default function AdminDashboard() {
<TextCustom bold size={30}> <TextCustom bold size={30}>
Main Dashboard Main Dashboard
</TextCustom> </TextCustom>
{listData.map((item, i) => ( {listData(countUser, countPortofolio).map((item, i) => (
<AdminComp_BoxDashboard key={i} item={item} /> <AdminComp_BoxDashboard key={i} item={item} />
))} ))}
</StackCustom> </StackCustom>
@@ -24,15 +42,15 @@ export default function AdminDashboard() {
); );
} }
const listData = [ const listData = (countUser: number, countPortofolio: number) => [
{ {
label: "User", label: "User",
value: 4, value: countUser,
icon: <Ionicons name="people" size={30} color={MainColor.yellow} />, icon: <Ionicons name="people" size={30} color={MainColor.yellow} />,
}, },
{ {
label: "Portofolio", label: "Portofolio",
value: 7, value: countPortofolio,
icon: ( icon: (
<Ionicons name="id-card-outline" size={30} color={MainColor.yellow} /> <Ionicons name="id-card-outline" size={30} color={MainColor.yellow} />
), ),

View File

@@ -1,3 +1,4 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { import {
ActionIcon, ActionIcon,
AlertDefaultSystem, AlertDefaultSystem,
@@ -18,97 +19,147 @@ import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButt
import AdminButtonReject from "@/components/_ShareComponent/Admin/ButtonReject"; import AdminButtonReject from "@/components/_ShareComponent/Admin/ButtonReject";
import AdminButtonReview from "@/components/_ShareComponent/Admin/ButtonReview"; import AdminButtonReview from "@/components/_ShareComponent/Admin/ButtonReview";
import { GridDetail_4_8 } from "@/components/_ShareComponent/GridDetail_4_8"; import { GridDetail_4_8 } from "@/components/_ShareComponent/GridDetail_4_8";
import { MainColor } from "@/constants/color-palet"; import ReportBox from "@/components/Box/ReportBox";
import { ICON_SIZE_BUTTON, TEXT_SIZE_LARGE } from "@/constants/constans-value"; import { ICON_SIZE_BUTTON, TEXT_SIZE_LARGE } from "@/constants/constans-value";
import AdminDonation_BoxOfDonationStory from "@/screens/Admin/Donation/BoxOfDonationStory"; import AdminDonation_BoxOfDonationStory from "@/screens/Admin/Donation/BoxOfDonationStory";
import { funUpdateStatusDonation } from "@/screens/Admin/Donation/funDonationUpdateStatus";
import { apiAdminDonationDetailById } from "@/service/api-admin/api-admin-donation";
import { colorBadgeStatus } from "@/utils/colorBadge";
import { formatCurrencyDisplay } from "@/utils/formatCurrencyDisplay";
import { Ionicons } from "@expo/vector-icons"; import { Ionicons } from "@expo/vector-icons";
import { router, useLocalSearchParams } from "expo-router"; import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
import _ from "lodash"; import _ from "lodash";
import React from "react"; import React from "react";
import { View } from "react-native"; import { View } from "react-native";
import Toast from "react-native-toast-message";
export default function AdminDonationDetail() { export default function AdminDonationDetail() {
const { id, status } = useLocalSearchParams(); const { id, status } = useLocalSearchParams();
const [openDrawer, setOpenDrawer] = React.useState(false); const [openDrawer, setOpenDrawer] = React.useState(false);
const colorBadge = () => { const [data, setData] = React.useState<any | null>(null);
if (status === "publish") { const [isLoading, setIsLoading] = React.useState(false);
return MainColor.green;
} else if (status === "review") { useFocusEffect(
return MainColor.orange; React.useCallback(() => {
} else if (status === "reject") { onLoadData();
return MainColor.red; }, [id])
} else { );
return MainColor.placeholder;
const onLoadData = async () => {
try {
const response = await apiAdminDonationDetailById({
id: id as string,
});
console.log("[RES GET BY ID]", JSON.stringify(response, null, 2));
if (response.success) {
setData(response.data);
}
} catch (error) {
console.log("[ERROR]", error);
setData(null);
} }
}; };
const listData = [ const listData = [
{ {
label: "Penggalang Dana", label: "Penggalang Dana",
value: `Bagas Banuna ${id}`, value: (data && data?.Author?.username) || "-",
}, },
{ {
label: "Judul", label: "Judul",
value: `Donasi Lorem ipsum dolor sit amet, consectetur adipisicing elit.`, value: (data && data?.title) || "-",
}, },
{ {
label: "Status", label: "Status",
value: ( value:
<BadgeCustom color={colorBadge()}> data && data?.DonasiMaster_Status?.name ? (
{_.startCase(status as string)} <BadgeCustom
</BadgeCustom> color={colorBadgeStatus({ status: data?.DonasiMaster_Status?.name })}
), >
{_.startCase(data?.DonasiMaster_Status?.name)}
</BadgeCustom>
) : (
"-"
),
}, },
{ {
label: "Durasi", label: "Durasi",
value: "30 Hari", value: (data && data?.DonasiMaster_Durasi?.name) + " hari" || "-",
}, },
{ {
label: "Target Dana", label: "Target Dana",
value: "Rp 10.000.000", value:
data && data?.target
? `Rp. ${formatCurrencyDisplay(data?.target)}`
: "-",
}, },
{ {
label: "Kategori", label: "Kategori",
value: "Kategori Donasi", value: (data && data?.DonasiMaster_Ketegori?.name) || "-",
}, },
// {
// label: "Total Donatur",
// value: "-",
// },
// {
// label: "Progress",
// value: "0 %",
// },
// {
// label: "Dana Terkumpul",
// value: "Rp 0",
// },
]; ];
const listPencarianDana = [ const listPencarianDana = [
{ {
label: "Total Dana Dicairkan", label: "Total Dana Dicairkan",
value: "Rp 0", value: `Rp ${(data && data?.totalPencairan) || 0}`,
}, },
{ {
label: "Sisa Dana", label: "Sisa Dana",
value: "Rp 0", value: `Rp ${(data && data?.terkumpul - data?.totalPencairan) || 0}`,
}, },
{ {
label: "Akumulasi Pencairan", label: "Akumulasi Pencairan",
value: "0 kali", value: `${(data && data?.totalPencairan) || 0} kali`,
}, },
{ {
label: "Bank Tujuan", label: "Bank Tujuan",
value: "BNI", value: (data && data?.namaBank) || "-",
}, },
{ {
label: "Nomor Rekening", label: "Nomor Rekening",
value: "123456789", value: (data && data?.rekening) || "-",
}, },
]; ];
const handleReport = async ({
changeStatus,
}: {
changeStatus: "publish" | "review" | "reject";
}) => {
try {
setIsLoading(true);
const response = await funUpdateStatusDonation({
id: id as string,
changeStatus,
data: data,
});
if (!response.success) {
Toast.show({
type: "error",
text1: "Update status gagal",
});
return;
}
Toast.show({
type: "success",
text1: "Update status berhasil",
});
router.back();
} catch (error) {
console.log("[ERROR]", error);
} finally {
setIsLoading(false);
}
};
const rightComponent = ( const rightComponent = (
<ActionIcon <ActionIcon
icon={<IconDot size={ICON_SIZE_BUTTON} />} icon={<IconDot size={ICON_SIZE_BUTTON} />}
@@ -118,8 +169,6 @@ export default function AdminDonationDetail() {
/> />
); );
return ( return (
<> <>
<ViewWrapper <ViewWrapper
@@ -147,6 +196,7 @@ export default function AdminDonationDetail() {
/> />
))} ))}
</StackCustom> </StackCustom>
<ButtonCustom <ButtonCustom
iconLeft={ iconLeft={
<Ionicons name="cash-outline" size={ICON_SIZE_BUTTON} /> <Ionicons name="cash-outline" size={ICON_SIZE_BUTTON} />
@@ -179,7 +229,7 @@ export default function AdminDonationDetail() {
<BaseBox> <BaseBox>
<StackCustom> <StackCustom>
<DummyLandscapeImage /> <DummyLandscapeImage imageId={data?.imageId || ""} />
{listData.map((item, i) => ( {listData.map((item, i) => (
<GridDetail_4_8 <GridDetail_4_8
key={i} key={i}
@@ -190,27 +240,33 @@ export default function AdminDonationDetail() {
</StackCustom> </StackCustom>
</BaseBox> </BaseBox>
<AdminDonation_BoxOfDonationStory data={data?.CeritaDonasi as any} />
{data &&
data?.catatan &&
(status === "review" || status === "reject") && (
<ReportBox text={data?.catatan} />
)}
{status === "review" && ( {status === "review" && (
<StackCustom> <StackCustom>
<AdminDonation_BoxOfDonationStory />
<AdminButtonReview <AdminButtonReview
isLoading={isLoading}
onPublish={() => { onPublish={() => {
AlertDefaultSystem({ AlertDefaultSystem({
title: "Publish", title: "Publish",
message: "Apakah anda yakin ingin mempublikasikan data ini?", message: "Apakah anda yakin ingin mempublikasikan data ini?",
textLeft: "Batal", textLeft: "Batal",
textRight: "Ya", textRight: "Ya",
onPressLeft: () => {
router.back();
},
onPressRight: () => { onPressRight: () => {
router.back(); handleReport({ changeStatus: "publish" });
}, },
}); });
}} }}
onReject={() => { onReject={() => {
router.push(`/admin/donation/${id}/reject-input`); router.push(
`/admin/donation/${id}/reject-input?status=${status}`
);
}} }}
/> />
</StackCustom> </StackCustom>
@@ -218,12 +274,12 @@ export default function AdminDonationDetail() {
{status === "reject" && ( {status === "reject" && (
<StackCustom> <StackCustom>
<AdminDonation_BoxOfDonationStory />
<AdminButtonReject <AdminButtonReject
title="Tambah Catatan" title="Tambah Catatan"
onReject={() => { onReject={() => {
router.push(`/admin/donation/${id}/reject-input`); router.push(
`/admin/donation/${id}/reject-input?status=${status}`
);
}} }}
/> />
</StackCustom> </StackCustom>

View File

@@ -1,3 +1,4 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { import {
ActionIcon, ActionIcon,
BadgeCustom, BadgeCustom,
@@ -10,22 +11,86 @@ import {
import { IconView } from "@/components/_Icon/IconComponent"; import { IconView } from "@/components/_Icon/IconComponent";
import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButtonAntTitle"; import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButtonAntTitle";
import { GridViewCustomSpan } from "@/components/_ShareComponent/GridViewCustomSpan"; import { GridViewCustomSpan } from "@/components/_ShareComponent/GridViewCustomSpan";
import { MainColor } from "@/constants/color-palet";
import { ICON_SIZE_BUTTON } from "@/constants/constans-value"; import { ICON_SIZE_BUTTON } from "@/constants/constans-value";
import { dummyMasterStatusTransaction } from "@/lib/dummy-data/_master/status-transaction"; import { apiAdminDonationListOfDonatur } from "@/service/api-admin/api-admin-donation";
import { router, useLocalSearchParams } from "expo-router"; import { apiMasterTransaction } from "@/service/api-client/api-master";
import React from "react"; import { colorBadgeTransaction } from "@/utils/colorBadge";
import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
import _ from "lodash";
import React, { useEffect } from "react";
import { View } from "react-native"; import { View } from "react-native";
import { Divider } from "react-native-paper"; import { Divider } from "react-native-paper";
export default function AdminDonasiListOfDonatur() { export default function AdminDonasiListOfDonatur() {
const { id } = useLocalSearchParams(); const { id } = useLocalSearchParams();
console.log("[ID >>]", id);
const [listData, setListData] = React.useState<any[] | null>(null);
const [master, setMaster] = React.useState<any[]>([]);
const [selectStatus, setSelectStatus] = React.useState<
"berhasil" | "gagal" | "proses" | "menunggu" | ""
>("");
useFocusEffect(
React.useCallback(() => {
onLoadData();
}, [id, selectStatus])
);
const onLoadData = async () => {
try {
const response = await apiAdminDonationListOfDonatur({
id: id as string,
status: "" as any,
});
console.log(
"[LIST OF DONATUR]",
JSON.stringify(response, null, 2)
);
if (response.success) {
setListData(response.data);
}
} catch (error) {
console.log("[ERROR]", error);
setListData([]);
}
};
useEffect(() => {
onLoadMaster();
}, []);
const onLoadMaster = async () => {
try {
const response = await apiMasterTransaction();
if (response.success) {
setMaster(response.data);
}
} catch (error) {
console.log("[ERROR]", error);
setMaster([]);
}
};
const searchComponent = ( const searchComponent = (
<View style={{ flexDirection: "row", gap: 5 }}> <View style={{ flexDirection: "row", gap: 5 }}>
<SelectCustom <SelectCustom
placeholder="Pilih status transaksi" placeholder="Pilih status transaksi"
data={dummyMasterStatusTransaction} data={
onChange={(value) => console.log(value)} _.isEmpty(master)
? []
: master?.map((item: any) => ({
label: item.name,
value: item.name
}))
}
onChange={(value: any) => {
console.log("[SELECT STATUS]", value);
const statusChooses = _.lowerCase(value);
setSelectStatus(statusChooses as any);
}}
styleContainer={{ width: "100%", marginBottom: 0 }} styleContainer={{ width: "100%", marginBottom: 0 }}
/> />
</View> </View>
@@ -59,7 +124,7 @@ export default function AdminDonasiListOfDonatur() {
/> />
<Divider /> <Divider />
<StackCustom> <StackCustom>
{Array.from({ length: 10 }).map((_, index) => ( {listData?.map((item: any, index: number) => (
<View key={index}> <View key={index}>
<GridViewCustomSpan <GridViewCustomSpan
span1={3} span1={3}
@@ -79,15 +144,15 @@ export default function AdminDonasiListOfDonatur() {
} }
component2={ component2={
<TextCustom bold align="center" truncate> <TextCustom bold align="center" truncate>
Bagas Banuna {item?.Author?.username || "-"}
</TextCustom> </TextCustom>
} }
component3={ component3={
<BadgeCustom <BadgeCustom
style={{ alignSelf: "center" }} style={{ alignSelf: "center" }}
color={MainColor.green} color={colorBadgeTransaction({status: item?.DonasiMaster_StatusInvoice?.name})}
> >
Berhasil {item?.DonasiMaster_StatusInvoice?.name}
</BadgeCustom> </BadgeCustom>
} }
/> />

View File

@@ -1,3 +1,4 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { import {
AlertDefaultSystem, AlertDefaultSystem,
BoxButtonOnFooter, BoxButtonOnFooter,
@@ -6,15 +7,84 @@ import {
} from "@/components"; } from "@/components";
import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButtonAntTitle"; import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButtonAntTitle";
import AdminButtonReject from "@/components/_ShareComponent/Admin/ButtonReject"; import AdminButtonReject from "@/components/_ShareComponent/Admin/ButtonReject";
import { router, useLocalSearchParams } from "expo-router"; import { funUpdateStatusDonation } from "@/screens/Admin/Donation/funDonationUpdateStatus";
import { useState } from "react"; import {
apiAdminDonationDetailById
} from "@/service/api-admin/api-admin-donation";
import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
import React from "react";
import Toast from "react-native-toast-message";
export default function AdminDonationRejectInput() { export default function AdminDonationRejectInput() {
const { id } = useLocalSearchParams(); const { id, status } = useLocalSearchParams();
const [value, setValue] = useState(id as string);
const [data, setData] = React.useState<any | null>(null);
const [isLoading, setIsLoading] = React.useState(false);
useFocusEffect(
React.useCallback(() => {
onLoadData();
}, [id])
);
const onLoadData = async () => {
try {
const response = await apiAdminDonationDetailById({
id: id as string,
});
if (response.success) {
setData(response.data.catatan);
}
} catch (error) {
console.log("[ERROR]", error);
setData(null);
}
};
const handleReport = async ({
changeStatus,
}: {
changeStatus: "publish" | "review" | "reject";
}) => {
try {
setIsLoading(true);
const response = await funUpdateStatusDonation({
id: id as string,
changeStatus,
data: data,
});
if (!response.success) {
Toast.show({
type: "error",
text1: "Report gagal",
});
return
}
Toast.show({
type: "success",
text1: "Report berhasil",
});
if (status === "review") {
router.replace(`/admin/donation/reject/status`);
} else if (status === "reject") {
router.back();
}
} catch (error) {
console.log("[ERROR]", error);
} finally {
setIsLoading(false);
}
};
const buttonSubmit = ( const buttonSubmit = (
<BoxButtonOnFooter> <BoxButtonOnFooter>
<AdminButtonReject <AdminButtonReject
isLoading={isLoading}
title="Reject" title="Reject"
onReject={() => onReject={() =>
AlertDefaultSystem({ AlertDefaultSystem({
@@ -22,12 +92,9 @@ export default function AdminDonationRejectInput() {
message: "Apakah anda yakin ingin menolak data ini?", message: "Apakah anda yakin ingin menolak data ini?",
textLeft: "Batal", textLeft: "Batal",
textRight: "Ya", textRight: "Ya",
onPressLeft: () => {
router.back();
},
onPressRight: () => { onPressRight: () => {
console.log("value:", value); handleReport({ changeStatus: "reject" });
router.replace(`/admin/donation/reject/status`);
}, },
}) })
} }
@@ -42,8 +109,8 @@ export default function AdminDonationRejectInput() {
headerComponent={<AdminBackButtonAntTitle title="Penolakan Donasi" />} headerComponent={<AdminBackButtonAntTitle title="Penolakan Donasi" />}
> >
<TextAreaCustom <TextAreaCustom
value={value} value={data}
onChangeText={setValue} onChangeText={setData}
placeholder="Masukan alasan" placeholder="Masukan alasan"
required required
showCount showCount

View File

@@ -1,69 +1,116 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { import {
ActionIcon, ActionIcon,
SearchInput, LoaderCustom,
Spacing, SearchInput,
TextCustom, StackCustom,
ViewWrapper TextCustom,
ViewWrapper
} from "@/components"; } from "@/components";
import AdminComp_BoxTitle from "@/components/_ShareComponent/Admin/BoxTitlePage"; import AdminComp_BoxTitle from "@/components/_ShareComponent/Admin/BoxTitlePage";
import AdminTitleTable from "@/components/_ShareComponent/Admin/TableTitle"; import AdminTitleTable from "@/components/_ShareComponent/Admin/TableTitle";
import AdminTableValue from "@/components/_ShareComponent/Admin/TableValue"; import AdminTableValue from "@/components/_ShareComponent/Admin/TableValue";
import AdminTitlePage from "@/components/_ShareComponent/Admin/TitlePage";
import { ICON_SIZE_BUTTON } from "@/constants/constans-value"; import { ICON_SIZE_BUTTON } from "@/constants/constans-value";
import { apiAdminDonation } from "@/service/api-admin/api-admin-donation";
import { Octicons } from "@expo/vector-icons"; import { Octicons } from "@expo/vector-icons";
import { router, useLocalSearchParams } from "expo-router"; import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
import _ from "lodash"; import _ from "lodash";
import { useCallback, useState } from "react";
import { Divider } from "react-native-paper"; import { Divider } from "react-native-paper";
export default function AdminDonationStatus() { export default function AdminDonationStatus() {
const { status } = useLocalSearchParams(); const { status } = useLocalSearchParams();
console.log("[STATUS]", status);
const [data, setData] = useState<any | null>(null);
const [search, setSearch] = useState<string>("");
const [loadData, setLoadData] = useState<boolean>(false);
useFocusEffect(
useCallback(() => {
onLoadData();
}, [status, search])
);
const onLoadData = async () => {
try {
setLoadData(true);
const response = await apiAdminDonation({
category: status as "publish" | "review" | "reject",
search,
});
console.log("[RES]", JSON.stringify(response, null, 2));
if (response.success) {
setData(response.data);
}
} catch (error) {
console.log("[ERROR]", error);
setData([]);
} finally {
setLoadData(false);
}
};
const rightComponent = ( const rightComponent = (
<SearchInput <SearchInput
containerStyle={{ width: "100%", marginBottom: 0 }} containerStyle={{ width: "100%", marginBottom: 0 }}
placeholder="Cari" placeholder="Cari"
value={search}
onChangeText={(value) => setSearch(value)}
/> />
); );
return ( return (
<> <>
<ViewWrapper <ViewWrapper headerComponent={<AdminTitlePage title="Donasi" />}>
headerComponent={ <StackCustom gap={"sm"}>
<AdminComp_BoxTitle <AdminComp_BoxTitle
title={`Donasi ${_.startCase(status as string)}`} title={`${_.startCase(status as string)}`}
rightComponent={rightComponent} rightComponent={rightComponent}
/> />
} <AdminTitleTable
> title1="Aksi"
<AdminTitleTable title2="Username"
title1="Aksi" title3="Judul Donasi"
title2="Username"
title3="Judul Donasi"
/>
<Spacing />
<Divider />
{Array.from({ length: 10 }).map((_, index) => (
<AdminTableValue
key={index}
value1={
<ActionIcon
icon={
<Octicons name="eye" size={ICON_SIZE_BUTTON} color="black" />
}
onPress={() => {
router.push(`/admin/donation/${index}/${status}`);
}}
/>
}
value2={<TextCustom truncate={1}>Username username</TextCustom>}
value3={
<TextCustom truncate={2}>
Lorem ipsum dolor sit amet consectetur adipisicing elit.
Blanditiis asperiores quidem deleniti architecto eaque et
nostrum, ad consequuntur eveniet quisquam quae voluptatum
ducimus! Dolorem nobis modi officia debitis, beatae mollitia.
</TextCustom>
}
/> />
))} <Divider />
{loadData ? (
<LoaderCustom />
) : _.isEmpty(data) ? (
<TextCustom align="center" size="small" color="gray">
Belum ada data
</TextCustom>
) : (
data?.map((item: any, index: number) => (
<AdminTableValue
key={index}
value1={
<ActionIcon
icon={
<Octicons
name="eye"
size={ICON_SIZE_BUTTON}
color="black"
/>
}
onPress={() => {
router.push(`/admin/donation/${item.id}/${status}`);
}}
/>
}
value2={<TextCustom truncate={1}>{item?.Author?.username || "-"}</TextCustom>}
value3={
<TextCustom truncate={2}>
{item?.title || "-"}
</TextCustom>
}
/>
))
)}
</StackCustom>
</ViewWrapper> </ViewWrapper>
</> </>
); );

View File

@@ -1,15 +1,67 @@
import { Spacing, StackCustom, ViewWrapper } from "@/components"; import { Spacing, StackCustom, ViewWrapper } from "@/components";
import { import {
IconList, IconList,
IconPublish, IconPublish,
IconReject, IconReject,
IconReview, IconReview,
} from "@/components/_Icon/IconComponent"; } from "@/components/_Icon/IconComponent";
import AdminComp_BoxDashboard from "@/components/_ShareComponent/Admin/BoxDashboard"; import AdminComp_BoxDashboard from "@/components/_ShareComponent/Admin/BoxDashboard";
import AdminTitlePage from "@/components/_ShareComponent/Admin/TitlePage"; import AdminTitlePage from "@/components/_ShareComponent/Admin/TitlePage";
import { MainColor } from "@/constants/color-palet"; import { MainColor } from "@/constants/color-palet";
import { apiAdminDonation } from "@/service/api-admin/api-admin-donation";
import { useFocusEffect } from "expo-router";
import { useState, useCallback } from "react";
export default function AdminDonation() { export default function AdminDonation() {
const [data, setData] = useState<any | null>(null);
useFocusEffect(
useCallback(() => {
onLoadData();
}, [])
);
const onLoadData = async () => {
try {
const response = await apiAdminDonation({
category: "dashboard",
});
console.log("[RES]", JSON.stringify(response, null, 2));
if (response.success) {
setData(response.data);
}
} catch (error) {
console.log("[ERROR]", error);
setData([]);
}
};
const listData = [
{
label: "Publish",
value: (data && data.publish) || 0,
icon: <IconPublish size={25} color={MainColor.green} />,
},
{
label: "Review",
value: (data && data.review) || 0,
icon: <IconReview size={25} color={MainColor.orange} />,
},
{
label: "Reject",
value: (data && data.reject) || 0,
icon: <IconReject size={25} color={MainColor.red} />,
},
{
label: "Kategori",
value: (data && data.categoryDonation) || 0,
icon: <IconList size={25} color={MainColor.white_gray} />,
},
];
return ( return (
<> <>
<ViewWrapper> <ViewWrapper>
@@ -24,26 +76,3 @@ export default function AdminDonation() {
</> </>
); );
} }
const listData = [
{
label: "Publish",
value: 4,
icon: <IconPublish size={25} color={MainColor.green} />,
},
{
label: "Review",
value: 7,
icon: <IconReview size={25} color={MainColor.orange} />,
},
{
label: "Reject",
value: 5,
icon: <IconReject size={25} color={MainColor.red} />,
},
{
label: "Kategori",
value: 4,
icon: <IconList size={25} color={MainColor.white_gray} />,
},
];

View File

@@ -1,112 +1,110 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { import {
ActionIcon, ActionIcon,
AlertDefaultSystem, AlertDefaultSystem,
BadgeCustom, BadgeCustom,
BaseBox, BaseBox,
DrawerCustom, DrawerCustom,
MenuDrawerDynamicGrid, LoaderCustom,
Spacing, MenuDrawerDynamicGrid,
StackCustom, Spacing,
TextCustom, StackCustom,
ViewWrapper, TextCustom,
ViewWrapper,
} from "@/components"; } from "@/components";
import { IconDot, IconList } from "@/components/_Icon/IconComponent"; import { IconDot, IconList } from "@/components/_Icon/IconComponent";
import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButtonAntTitle"; import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButtonAntTitle";
import AdminButtonReject from "@/components/_ShareComponent/Admin/ButtonReject"; import AdminButtonReject from "@/components/_ShareComponent/Admin/ButtonReject";
import AdminButtonReview from "@/components/_ShareComponent/Admin/ButtonReview"; import AdminButtonReview from "@/components/_ShareComponent/Admin/ButtonReview";
import { GridDetail_4_8 } from "@/components/_ShareComponent/GridDetail_4_8"; import { GridDetail_4_8 } from "@/components/_ShareComponent/GridDetail_4_8";
import { MainColor } from "@/constants/color-palet"; import ReportBox from "@/components/Box/ReportBox";
import { ICON_SIZE_BUTTON } from "@/constants/constans-value"; import { ICON_SIZE_BUTTON } from "@/constants/constans-value";
import dayjs from "dayjs"; import { useAuth } from "@/hooks/use-auth";
import { router, useLocalSearchParams } from "expo-router"; import { funUpdateStatusEvent } from "@/screens/Admin/Event/funUpdateStatus";
import { apiAdminEventById } from "@/service/api-admin/api-admin-event";
import { DEEP_LINK_URL } from "@/service/api-config";
import { colorBadgeStatus } from "@/utils/colorBadge";
import { dateTimeView } from "@/utils/dateTimeView";
import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
import _ from "lodash"; import _ from "lodash";
import React from "react"; import React, { useCallback } from "react";
import QRCode from "react-native-qrcode-svg"; import QRCode from "react-native-qrcode-svg";
import Toast from "react-native-toast-message";
export default function AdminEventDetail() { export default function AdminEventDetail() {
const { user } = useAuth();
const { id, status } = useLocalSearchParams(); const { id, status } = useLocalSearchParams();
const [openDrawer, setOpenDrawer] = React.useState(false); const [openDrawer, setOpenDrawer] = React.useState(false);
const colorBadge = () => { const [data, setData] = React.useState<any | null>(null);
if (status === "publish") { const [loadData, setLoadData] = React.useState(false);
return MainColor.green; const deepLinkURL = `${DEEP_LINK_URL}/--/event/${id}/confirmation?userId=${user?.id}`;
} else if (status === "review") { useFocusEffect(
return MainColor.orange; useCallback(() => {
} else if (status === "reject") { onLoadData();
return MainColor.red; }, [id])
} else { );
return MainColor.placeholder; const onLoadData = async () => {
try {
setLoadData(true);
const response = await apiAdminEventById({
id: id as string,
});
if (response.success) {
setData(response.data);
}
} catch (error) {
console.log("[ERROR]", error);
} finally {
setLoadData(false);
} }
}; };
const listData = [ const listData = [
{ {
label: "Pembuat Event", label: "Pembuat Event",
value: `Bagas Banuna ${id}`, value: (data && data?.Author?.username) || "-",
}, },
{ {
label: "Judul Event", label: "Judul Event",
value: `Event 123`, value: (data && data?.title) || "-",
}, },
{ {
label: "Status", label: "Status",
value: ( value:
<BadgeCustom color={colorBadge()}> (data && (
{_.startCase(status as string)} <BadgeCustom color={colorBadgeStatus({ status: status as string })}>
</BadgeCustom> {_.startCase(status as string)}
), </BadgeCustom>
)) ||
"-",
}, },
{ {
label: "Lokasi", label: "Lokasi",
value: "Lokasi Event", value: (data && data?.lokasi) || "-",
}, },
{ {
label: "Tipe Acara", label: "Tipe Acara",
value: "Tipe Acara", value: (data && data?.EventMaster_TipeAcara?.name) || "-",
}, },
{ {
label: "Mulai Event", label: "Mulai Event",
value: dayjs().format("DD/MM/YYYY HH:mm:ss"), value:
(data && data?.tanggal && dateTimeView({ date: data?.tanggal })) || "-",
}, },
{ {
label: "Event Berakhir", label: "Event Berakhir",
value: dayjs().add(3, "day").format("DD/MM/YYYY HH:mm:ss"), value:
(data &&
data?.tanggalSelesai &&
dateTimeView({ date: data?.tanggalSelesai })) ||
"-",
}, },
{ {
label: "Deskripsi", label: "Deskripsi",
value: "Lorem ipsum dolor sit amet consectetur adipisicing elit.", value: (data && data?.deskripsi) || "-",
}, },
// {
// label: "Daftar Tipe Acara",
// value: (
// <>
// <List.Item
// title={<TextCustom>Pilihan 1</TextCustom>}
// left={(props) => (
// <List.Icon {...props} icon="circle" color={MainColor.yellow} />
// )}
// />
// <List.Item
// title={<TextCustom>Pilihan 2</TextCustom>}
// left={(props) => (
// <List.Icon {...props} icon="circle" color={MainColor.yellow} />
// )}
// />
// <List.Item
// title={<TextCustom>Pilihan 3</TextCustom>}
// left={(props) => (
// <List.Icon {...props} icon="circle" color={MainColor.yellow} />
// )}
// />
// <List.Item
// title={<TextCustom>Pilihan 4</TextCustom>}
// left={(props) => (
// <List.Icon {...props} icon="circle" color={MainColor.yellow} />
// )}
// />
// </>
// ),
// },
]; ];
const rightComponent = ( const rightComponent = (
@@ -118,6 +116,31 @@ export default function AdminEventDetail() {
/> />
); );
const handlerSubmit = async () => {
try {
const response = await funUpdateStatusEvent({
id: id as string,
changeStatus: "publish",
});
if (!response.success) {
Toast.show({
type: "error",
text1: "Gagal mempublikasikan event",
});
return;
}
Toast.show({
type: "success",
text1: "Event berhasil dipublikasikan",
});
router.back();
} catch (error) {
console.log("[ERROR]", error);
}
};
return ( return (
<> <>
<ViewWrapper <ViewWrapper
@@ -125,7 +148,7 @@ export default function AdminEventDetail() {
<AdminBackButtonAntTitle <AdminBackButtonAntTitle
title={`Detail Data`} title={`Detail Data`}
rightComponent={ rightComponent={
(status === "publish" || status === "riwayat") && rightComponent (status === "publish" || status === "history") && rightComponent
} }
/> />
} }
@@ -143,19 +166,30 @@ export default function AdminEventDetail() {
<Spacing /> <Spacing />
</BaseBox> </BaseBox>
{(status === "publish" || status === "riwayat") && (
{data &&
data?.catatan &&
(status === "reject" || status === "review") && (
<ReportBox text={data?.catatan} />
)}
{(status === "publish" || status === "history") && (
<BaseBox> <BaseBox>
<StackCustom style={{ alignItems: "center" }}> <StackCustom style={{ alignItems: "center" }}>
<TextCustom bold>QR Code Event</TextCustom> <TextCustom bold>QR Code Event</TextCustom>
<QRCode {loadData ? (
value="https://google.com" <LoaderCustom />
size={200} ) : (
// logo={require("@/assets/images/logo-hipmi.png")} <QRCode
// logoSize={70} value={deepLinkURL}
// logoBackgroundColor="transparent" size={200}
// logoBorderRadius={50} // logo={require("@/assets/images/logo-hipmi.png")}
// color="black" // logoSize={70}
/> // logoBackgroundColor="transparent"
// logoBorderRadius={50}
// color="black"
/>
)}
</StackCustom> </StackCustom>
</BaseBox> </BaseBox>
)} )}
@@ -168,16 +202,11 @@ export default function AdminEventDetail() {
message: "Apakah anda yakin ingin mempublikasikan data ini?", message: "Apakah anda yakin ingin mempublikasikan data ini?",
textLeft: "Batal", textLeft: "Batal",
textRight: "Ya", textRight: "Ya",
onPressLeft: () => { onPressRight: () => handlerSubmit(),
router.back();
},
onPressRight: () => {
router.back();
},
}); });
}} }}
onReject={() => { onReject={() => {
router.push(`/admin/event/${id}/reject-input`); router.push(`/admin/event/${id}/reject-input?status=${status}`);
}} }}
/> />
)} )}
@@ -186,7 +215,7 @@ export default function AdminEventDetail() {
<AdminButtonReject <AdminButtonReject
title="Tambah Catatan" title="Tambah Catatan"
onReject={() => { onReject={() => {
router.push(`/admin/event/${id}/reject-input`); router.push(`/admin/event/${id}/reject-input?status=${status}`);
}} }}
/> />
)} )}

View File

@@ -1,41 +1,81 @@
import { BadgeCustom, BaseBox, Grid, TextCustom, ViewWrapper } from "@/components"; /* eslint-disable react-hooks/exhaustive-deps */
import {
BadgeCustom,
BaseBox,
Grid,
LoaderCustom,
StackCustom,
TextCustom,
ViewWrapper,
} from "@/components";
import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButtonAntTitle"; import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButtonAntTitle";
import { MainColor } from "@/constants/color-palet"; import { apiAdminEventListOfParticipants } from "@/service/api-admin/api-admin-event";
import { useFocusEffect, useLocalSearchParams } from "expo-router";
import _ from "lodash";
import { useCallback, useState } from "react";
export default function AdminEventListOfParticipants() { export default function AdminEventListOfParticipants() {
const { id } = useLocalSearchParams();
const [listData, setListData] = useState<any[] | null>(null);
const [loadData, setLoadData] = useState(false);
const isPresent = ({id}: {id: number}) => { useFocusEffect(
const check = id % 3 * 3; useCallback(() => {
if (check === 0) { onLoadData();
return true; }, [id])
} else { );
return false;
} const onLoadData = async () => {
try {
setLoadData(true);
const response = await apiAdminEventListOfParticipants({
id: id as string,
});
if (response.success) {
setListData(response.data);
}
} catch (error) {
console.log("[ERROR]", error);
} finally {
setLoadData(false);
} }
};
return ( return (
<> <>
<ViewWrapper <ViewWrapper
headerComponent={<AdminBackButtonAntTitle title="Daftar Peserta" />} headerComponent={<AdminBackButtonAntTitle title="Daftar Peserta" />}
> >
{Array.from({ length: 10 }).map((item, index) => ( {loadData ? (
<BaseBox key={index}> <LoaderCustom />
<Grid> ) : _.isEmpty(listData) ? (
<Grid.Col span={6}> <TextCustom align="center" color="gray">
<TextCustom bold>Username {index + 1}</TextCustom> Belum ada peserta
<TextCustom>+6282123456789</TextCustom> </TextCustom>
</Grid.Col> ) : (
<Grid.Col span={6} style={{ justifyContent: "center" }}> listData?.map((item: any, index: number) => (
<BadgeCustom <BaseBox key={index}>
style={{ alignSelf: "flex-end" }} <Grid>
color={isPresent({id: index}) ? MainColor.green : MainColor.red} <Grid.Col span={6}>
> <StackCustom gap={"sm"}>
{isPresent({id: index}) ? "Hadir" : "Tidak Hadir"} <TextCustom bold truncate>{item?.User?.username}</TextCustom>
</BadgeCustom> <TextCustom>+{item?.User?.nomor}</TextCustom>
</Grid.Col> </StackCustom>
</Grid> </Grid.Col>
</BaseBox> <Grid.Col span={6} style={{ justifyContent: "center" }}>
))} <BadgeCustom
style={{ alignSelf: "flex-end" }}
color={item?.isPresent ? "green" : "red"}
>
{item?.isPresent ? "Hadir" : "Tidak Hadir"}
</BadgeCustom>
</Grid.Col>
</Grid>
</BaseBox>
))
)}
</ViewWrapper> </ViewWrapper>
</> </>
); );

Some files were not shown because too many files have changed in this diff Show More