Compare commits

...

48 Commits

Author SHA1 Message Date
4625831377 Integrasi API: Admin Investasi
Fix:
- app/(application)/(user)/investment/(tabs)/index.tsx
- app/(application)/admin/investment/[id]/[status]/transaction-detail.tsx
- app/(application)/admin/investment/[id]/list-of-investor.tsx
- screens/Invesment/BoxBerandaSection.tsx
- screens/Invesment/DetailDataPublishSection.tsx
- service/api-admin/api-admin-investment.ts

### No Issue
2025-10-30 17:36:42 +08:00
ebd6107c36 Integrasi API: Investment:
Add:
- screens/Invesment/BoxBerandaSection.tsx

Fix:
- app/(application)/(user)/investment/(tabs)/index.tsx
- screens/Donation/BoxPublish.tsx
- screens/Invesment/BoxProgressSection.tsx
- screens/Invesment/DetailDataPublishSection.tsx

### No Issue
2025-10-30 16:38:24 +08:00
f23cfe1107 Integrasi API: Investment & Admin Investment
Add:
- components/_ShareComponent/NoDataText.tsx
- service/api-admin/api-admin-investment.ts

Fix:
- app/(application)/(user)/investment/(tabs)/index.tsx
- app/(application)/admin/investment/[id]/[status]/index.tsx
- app/(application)/admin/investment/[id]/reject-input.tsx
- app/(application)/admin/investment/[status]/status.tsx
- app/(application)/admin/investment/index.tsx
- screens/Invesment/DetailDataPublishSection.tsx

### No Issue
2025-10-30 15:13:33 +08:00
b3209dc7ee Integrasi API: Donation & Admin Donation
Fix:
- app/(application)/(user)/donation/[id]/fund-disbursement.tsx
- app/(application)/(user)/donation/[id]/list-of-donatur.tsx
- app/(application)/admin/donation/[id]/[status]/index.tsx
- app/(application)/admin/donation/[id]/detail-disbursement-of-funds.tsx
- app/(application)/admin/donation/[id]/disbursement-of-funds.tsx
- app/(application)/admin/donation/[id]/list-disbursement-of-funds.tsx
- service/api-admin/api-admin-donation.ts
- service/api-client/api-donation.ts
- utils/pickFile.ts: Sudah bisa memilih ukuran crop tapi hanya di android

### No issue
2025-10-29 17:35:18 +08:00
1e1b18f860 Integrasi API: Donation & Admin Donation
Fix:
- app/(application)/(user)/donation/[id]/(transaction-flow)/[invoiceId]/invoice.tsx
- app/(application)/(user)/donation/[id]/index.tsx
- app/(application)/admin/donation/[id]/[status]/index.tsx
- app/(application)/admin/donation/[id]/[status]/transaction-detail.tsx
- app/(application)/admin/donation/[id]/list-of-donatur.tsx
- components/Select/SelectCustom.tsx
- context/AuthContext.tsx
- screens/Donation/BoxPublish.tsx
- screens/Donation/ProgressSection.tsx
- service/api-admin/api-admin-donation.ts

### NO Issue
2025-10-28 17:45:13 +08:00
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
29b65aeebf Forum
Fix:
- Integrasi API ke semua tampilan

### No Issue
2025-09-26 17:43:50 +08:00
18beb09b42 Forum
Fix:
- Tampilan beranda forum & bisa melakukan search dan sudah terintegrasi API
- Fitur hapus edit dan ubah status sudah terintegrasi API
- List komentar sudah bisa muncul dan bisa mengahpus

### No Issue
2025-09-26 14:46:29 +08:00
54af104f8a Forum
Add:
- api-client/api-forum

Fix:
- Integrasi API: create dan beranda file

### No Issue
2025-09-24 17:30:06 +08:00
8c5602b809 Collaboration
Add:
- Collaboration/GroupChatSection.tsx : fitur room chat

Fix:
- Clear code: Hapus console pada beberapa file

### No Issue
2025-09-24 15:28:16 +08:00
99f058a92f Collaboration
Add:
- (user)/collaboration/[id]/select-of-participants

Fix:
- Integrasi ke api di bagian beranda , partisipan dan group

### No Issue
2025-09-23 17:41:03 +08:00
821a211f58 Collaboration
Fix:
- Integrasi API: Beranda, create, list partisipan, check sudah berpartisipasi ?

### No Issue
2025-09-22 17:31:40 +08:00
272 changed files with 17338 additions and 7507 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

@@ -26,6 +26,7 @@ export default {
}, },
edgeToEdgeEnabled: true, edgeToEdgeEnabled: true,
package: 'com.bip.hipmimobileapp', package: 'com.bip.hipmimobileapp',
// softwareKeyboardLayoutMode: 'resize', // option: untuk mengatur keyboard pada room chst collaboration
}, },
web: { web: {
@@ -68,5 +69,7 @@ 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,
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={{
@@ -118,14 +117,13 @@ export default function UserLayout() {
headerLeft: () => <BackButton />, headerLeft: () => <BackButton />,
}} }}
/> />
<Stack.Screen {/* <Stack.Screen
name="collaboration/[id]/detail-participant" name="collaboration/[id]/detail-participant"
options={{ options={{
title: "Partisipasi Proyek", title: "Partisipasi Proyek",
headerLeft: () => <BackButton />, headerLeft: () => <BackButton />,
}} }}
/> /> */}
<Stack.Screen <Stack.Screen
name="collaboration/[id]/edit" name="collaboration/[id]/edit"
options={{ options={{
@@ -133,6 +131,20 @@ export default function UserLayout() {
headerLeft: () => <BackButton />, headerLeft: () => <BackButton />,
}} }}
/> />
<Stack.Screen
name="collaboration/[id]/create-pacticipants"
options={{
title: "Ajukan Partisipasi",
headerLeft: () => <BackButton />,
}}
/>
<Stack.Screen
name="collaboration/[id]/select-of-participants"
options={{
title: "Pilih Partisipan",
headerLeft: () => <BackButton />,
}}
/>
{/* ========== End Collaboration Section ========= */} {/* ========== End Collaboration Section ========= */}
@@ -173,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" />,
}} }}
/> />
@@ -436,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: () => (
@@ -450,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: () => (
@@ -464,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

@@ -1,38 +1,96 @@
import { BaseBox, Grid, TextCustom } from "@/components"; /* eslint-disable @typescript-eslint/no-unused-vars */
/* eslint-disable react-hooks/exhaustive-deps */
import {
BaseBox,
Grid,
LoaderCustom,
StackCustom,
TextCustom,
} from "@/components";
import ViewWrapper from "@/components/_ShareComponent/ViewWrapper"; import ViewWrapper from "@/components/_ShareComponent/ViewWrapper";
import { MainColor } from "@/constants/color-palet"; import { MainColor } from "@/constants/color-palet";
import { useAuth } from "@/hooks/use-auth";
import { apiCollaborationGetAll } from "@/service/api-client/api-collaboration";
import { Feather } from "@expo/vector-icons"; import { Feather } from "@expo/vector-icons";
import { useFocusEffect } from "expo-router";
import _ from "lodash";
import { useState, useCallback } from "react";
export default function CollaborationGroup() { export default function CollaborationGroup() {
const { user } = useAuth();
const [listData, setListData] = useState<any[]>();
const [loadingGetData, setLoadingGetData] = useState(false);
useFocusEffect(
useCallback(() => {
onLoadData();
}, [user?.id])
);
const onLoadData = async () => {
try {
setLoadingGetData(true);
const response = await apiCollaborationGetAll({
category: "group",
authorId: user?.id,
});
if (response.success) {
setListData(response.data);
}
} catch (error) {
console.log("[ERROR]", error);
} finally {
setLoadingGetData(false);
}
};
return ( return (
<ViewWrapper hideFooter> <ViewWrapper hideFooter>
{Array.from({ length: 10 }).map((_, index) => ( {loadingGetData ? (
<BaseBox <LoaderCustom />
key={index} ) : _.isEmpty(listData) ? (
paddingBlock={5} <TextCustom align="center">Tidak ada data</TextCustom>
href={`/collaboration/${index}/${generateProjectName()}/room-chat`} ) : (
> <StackCustom>
<Grid> {listData?.map((item: any, index: any) => (
<Grid.Col span={10}> <BaseBox
<TextCustom bold>{generateProjectName()}</TextCustom> key={index}
<TextCustom size="small">2 Anggota</TextCustom> paddingBlock={5}
</Grid.Col> href={`/collaboration/${item?.ProjectCollaboration_RoomChat?.id}/${item?.ProjectCollaboration_RoomChat?.name}/room-chat`}
<Grid.Col
span={2}
style={{ alignItems: "flex-end", justifyContent: "center" }}
> >
<Feather name="chevron-right" size={20} color={MainColor.white} /> <Grid>
</Grid.Col> <Grid.Col span={10}>
</Grid> <TextCustom bold>
</BaseBox> {item?.ProjectCollaboration_RoomChat?.name}
))} </TextCustom>
<TextCustom size="small">
{
item?.ProjectCollaboration_RoomChat
?.ProjectCollaboration_AnggotaRoomChat?.length
}{" "}
Anggota
</TextCustom>
</Grid.Col>
<Grid.Col
span={2}
style={{ alignItems: "flex-end", justifyContent: "center" }}
>
<Feather
name="chevron-right"
size={20}
color={MainColor.white}
/>
</Grid.Col>
</Grid>
</BaseBox>
))}
</StackCustom>
)}
</ViewWrapper> </ViewWrapper>
); );
} }
function generateProjectName() { function generateProjectName() {
const adjectives = [ const adjectives = [
"Blue", "Blue",
@@ -65,4 +123,4 @@ function generateProjectName() {
const randomNoun = nouns[Math.floor(Math.random() * nouns.length)]; const randomNoun = nouns[Math.floor(Math.random() * nouns.length)];
return randomAdjective + randomNoun; return randomAdjective + randomNoun;
} }

View File

@@ -1,8 +1,40 @@
import { FloatingButton, ViewWrapper } from "@/components"; import {
FloatingButton,
LoaderCustom,
TextCustom,
ViewWrapper,
} from "@/components";
import Collaboration_BoxPublishSection from "@/screens/Collaboration/BoxPublishSection"; import Collaboration_BoxPublishSection from "@/screens/Collaboration/BoxPublishSection";
import { router } from "expo-router"; import { apiCollaborationGetAll } from "@/service/api-client/api-collaboration";
import { router, useFocusEffect } from "expo-router";
import _ from "lodash";
import { useCallback, useState } from "react";
export default function CollaborationBeranda() { export default function CollaborationBeranda() {
const [listData, setListData] = useState<any[]>();
const [loadingGetData, setLoadingGetData] = useState(false);
useFocusEffect(
useCallback(() => {
onLoadData();
}, [])
);
const onLoadData = async () => {
try {
setLoadingGetData(true);
const response = await apiCollaborationGetAll({
category: "beranda",
});
setListData(response.data);
} catch (error) {
console.log("[ERROR]", error);
} finally {
setLoadingGetData(false);
}
};
return ( return (
<> <>
<ViewWrapper <ViewWrapper
@@ -15,13 +47,19 @@ export default function CollaborationBeranda() {
/> />
} }
> >
{Array.from({ length: 10 }).map((_, index) => ( {loadingGetData ? (
<Collaboration_BoxPublishSection <LoaderCustom />
key={index} ) : _.isEmpty(listData) ? (
id={index.toString()} <TextCustom align="center">Tidak ada data</TextCustom>
href={`/collaboration/${index}`} ) : (
/> listData?.map((item: any, index: number) => (
))} <Collaboration_BoxPublishSection
key={index}
href={`/collaboration/${item.id}`}
data={item}
/>
))
)}
</ViewWrapper> </ViewWrapper>
</> </>
); );

View File

@@ -1,15 +1,46 @@
import { ButtonCustom, Spacing } from "@/components"; /* eslint-disable react-hooks/exhaustive-deps */
import { ButtonCustom, LoaderCustom, Spacing, TextCustom } from "@/components";
import ViewWrapper from "@/components/_ShareComponent/ViewWrapper"; import ViewWrapper from "@/components/_ShareComponent/ViewWrapper";
import { AccentColor, MainColor } from "@/constants/color-palet"; import { AccentColor, MainColor } from "@/constants/color-palet";
import { useAuth } from "@/hooks/use-auth";
import Collaboration_BoxPublishSection from "@/screens/Collaboration/BoxPublishSection"; import Collaboration_BoxPublishSection from "@/screens/Collaboration/BoxPublishSection";
import { useState } from "react"; import { apiCollaborationGetAll } from "@/service/api-client/api-collaboration";
import { useFocusEffect } from "expo-router";
import _ from "lodash";
import { useCallback, useState } from "react";
import { View } from "react-native"; import { View } from "react-native";
export default function CollaborationParticipans() { export default function CollaborationParticipans() {
const [activeCategory, setActiveCategory] = useState<string | null>( const [activeCategory, setActiveCategory] = useState<
"participant" "participant" | "my-project"
>("participant");
const { user } = useAuth();
const [listData, setListData] = useState<any[]>();
const [loadingGetData, setLoadingGetData] = useState(false);
useFocusEffect(
useCallback(() => {
onLoadData();
}, [activeCategory])
); );
const onLoadData = async () => {
try {
setLoadingGetData(true);
const response = await apiCollaborationGetAll({
category:
activeCategory === "participant" ? "participant" : "my-project",
authorId: user?.id,
});
setListData(response.data);
} catch (error) {
console.log("[ERROR]", error);
} finally {
setLoadingGetData(false);
}
};
const handlePress = (item: any) => { const handlePress = (item: any) => {
setActiveCategory(item); setActiveCategory(item);
// tambahkan logika lain seperti filter dsb. // tambahkan logika lain seperti filter dsb.
@@ -41,13 +72,13 @@ export default function CollaborationParticipans() {
<Spacing width={"2%"} /> <Spacing width={"2%"} />
<ButtonCustom <ButtonCustom
backgroundColor={ backgroundColor={
activeCategory === "main" ? MainColor.yellow : AccentColor.blue activeCategory === "my-project" ? MainColor.yellow : AccentColor.blue
} }
textColor={ textColor={
activeCategory === "main" ? MainColor.black : MainColor.white activeCategory === "my-project" ? MainColor.black : MainColor.white
} }
style={{ width: "49%" }} style={{ width: "49%" }}
onPress={() => handlePress("main")} onPress={() => handlePress("my-project")}
> >
Proyek Saya Proyek Saya
</ButtonCustom> </ButtonCustom>
@@ -56,22 +87,27 @@ export default function CollaborationParticipans() {
return ( return (
<ViewWrapper hideFooter headerComponent={headerComponent}> <ViewWrapper hideFooter headerComponent={headerComponent}>
{Array.from({ length: 10 }).map((_, index) => ( {loadingGetData ? (
<Collaboration_BoxPublishSection <LoaderCustom />
key={index.toString()} ) : _.isEmpty(listData) ? (
id={index.toString()} <TextCustom align="center">Tidak ada data</TextCustom>
username={` ${ ) : activeCategory === "participant" ? (
activeCategory === "participant" listData?.map((item: any, index: number) => (
? "Partisipasi Proyek" <Collaboration_BoxPublishSection
: "Proyek Saya" key={index.toString()}
}`} data={item?.ProjectCollaboration}
href={ href={`/collaboration/${item?.ProjectCollaboration?.id}/detail-participant`}
activeCategory === "participant" />
? `/collaboration/${index}/detail-participant` ))
: `/collaboration/${index}/detail-project-main` ) : (
} listData?.map((item: any, index: number) => (
/> <Collaboration_BoxPublishSection
))} key={index.toString()}
data={item}
href={`/collaboration/${item?.id}/detail-project-main`}
/>
))
)}
</ViewWrapper> </ViewWrapper>
); );
} }

View File

@@ -1,17 +1,41 @@
/* eslint-disable react-hooks/exhaustive-deps */
/* eslint-disable @typescript-eslint/no-unused-vars */ /* eslint-disable @typescript-eslint/no-unused-vars */
import { import {
AvatarUsernameAndOtherComponent, AvatarUsernameAndOtherComponent,
BackButton, BackButton,
BaseBox,
BoxWithHeaderSection, BoxWithHeaderSection,
Grid, Grid,
StackCustom, StackCustom,
TextCustom, TextCustom,
ViewWrapper, ViewWrapper,
} from "@/components"; } from "@/components";
import { Stack, useLocalSearchParams } from "expo-router"; import { apiCollaborationGroup } from "@/service/api-client/api-collaboration";
import { Stack, useFocusEffect, useLocalSearchParams } from "expo-router";
import { useState, useCallback } from "react";
export default function CollaborationRoomInfo() { export default function CollaborationRoomInfo() {
const { id, detail } = useLocalSearchParams(); const { id, detail } = useLocalSearchParams();
const [data, setData] = useState<any>();
useFocusEffect(
useCallback(() => {
onLoadData();
}, [id])
);
const onLoadData = async () => {
try {
const response = await apiCollaborationGroup({ id: id as string });
if (response.success) {
setData(response.data);
}
} catch (error) {
console.log("[ERROR]", error);
}
};
return ( return (
<> <>
<Stack.Screen <Stack.Screen
@@ -24,7 +48,7 @@ export default function CollaborationRoomInfo() {
<ViewWrapper> <ViewWrapper>
<BoxWithHeaderSection> <BoxWithHeaderSection>
<StackCustom> <StackCustom>
{listData.map((item, index) => ( {listData({ data }).map((item, index) => (
<Grid key={index}> <Grid key={index}>
<Grid.Col span={4}> <Grid.Col span={4}>
<TextCustom bold>{item.title}</TextCustom> <TextCustom bold>{item.title}</TextCustom>
@@ -37,37 +61,42 @@ export default function CollaborationRoomInfo() {
</StackCustom> </StackCustom>
</BoxWithHeaderSection> </BoxWithHeaderSection>
<BoxWithHeaderSection> <BaseBox>
{Array.from({ length: 10 }).map((_, index) => ( <StackCustom gap={10}>
<AvatarUsernameAndOtherComponent key={index} avatarHref={`/profile/${index}`} /> {data?.ProjectCollaboration_AnggotaRoomChat?.map(
))} (item: any, index: number) => (
</BoxWithHeaderSection> <AvatarUsernameAndOtherComponent
key={index}
avatarHref={`/profile/${item?.User?.Profile?.id}`}
name={item?.User?.username}
avatar={item?.User?.Profile?.imageId}
/>
)
)}
</StackCustom>
</BaseBox>
</ViewWrapper> </ViewWrapper>
</> </>
); );
} }
const listData = [ const listData = ({ data }: { data: any }) => [
{ {
title: "Judul Proyek", title: "Judul Proyek",
value: "Judul Proyek", value: data?.ProjectCollaboration?.title || "-",
}, },
{ {
title: "Industri", title: "Industri",
value: "Pilihan Industri", value:
}, data?.ProjectCollaboration?.ProjectCollaborationMaster_Industri?.name ||
{ "-",
title: "Deskripsi",
value: "Deskripsi Proyek",
}, },
{ {
title: "Tujuan Proyek", title: "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.",
}, },
{ {
title: "Keuntungan Proyek", title: "Keuntungan Proyek",
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

@@ -1,68 +1,13 @@
import { import { BackButton } from "@/components";
BackButton, import { MainColor } from "@/constants/color-palet";
BoxButtonOnFooter,
Grid,
TextCustom,
TextInputCustom,
ViewWrapper,
} from "@/components";
import { AccentColor, MainColor } from "@/constants/color-palet";
import { ICON_SIZE_SMALL } from "@/constants/constans-value"; import { ICON_SIZE_SMALL } from "@/constants/constans-value";
import ChatScreen from "@/screens/Collaboration/GroupChatSection";
import { Feather } from "@expo/vector-icons"; import { Feather } from "@expo/vector-icons";
import { router, Stack, useLocalSearchParams } from "expo-router"; import { router, Stack, useLocalSearchParams } from "expo-router";
import { StyleSheet, TouchableOpacity, View } from "react-native";
export default function CollaborationRoomChat() { export default function CollaborationRoomChat() {
const { id, detail } = useLocalSearchParams(); const { id, detail } = useLocalSearchParams();
const inputChat = () => {
return (
<>
<BoxButtonOnFooter>
{/* <View style={{flexDirection: 'row', alignItems: 'center'}}>
<TextInputCustom placeholder="Ketik pesan..." />
<TouchableOpacity
activeOpacity={0.5}
onPress={() => console.log("Send")}
style={{
backgroundColor: AccentColor.blue,
padding: 10,
borderRadius: 50,
}}
>
<Feather name="send" size={30} color={MainColor.white} />
</TouchableOpacity>
</View> */}
<Grid>
<Grid.Col span={9}>
<TextInputCustom placeholder="Ketik pesan..." />
</Grid.Col>
<Grid.Col span={1}>
<View />
</Grid.Col>
<Grid.Col span={2} style={{ alignItems: "center" }}>
<TouchableOpacity
activeOpacity={0.5}
onPress={() => console.log("Send")}
style={{
backgroundColor: AccentColor.blue,
padding: 10,
borderRadius: 50,
}}
>
<Feather
name="send"
size={30}
color={MainColor.white}
/>
</TouchableOpacity>
</Grid.Col>
</Grid>
</BoxButtonOnFooter>
</>
);
};
return ( return (
<> <>
<Stack.Screen <Stack.Screen
@@ -79,114 +24,8 @@ export default function CollaborationRoomChat() {
), ),
}} }}
/> />
<ViewWrapper footerComponent={inputChat()}>
{dummyData.map((item, index) => ( <ChatScreen id={id as string} />
<View
key={index}
style={[
styles.messageRow,
item.role === 1 ? styles.rightAlign : styles.leftAlign,
]}
>
<View
style={[
styles.bubble,
item.role === 1 ? styles.bubbleRight : styles.bubbleLeft,
]}
>
<TextCustom style={styles.sender}>{item.nama}</TextCustom>
<TextCustom style={styles.message}>{item.chat}</TextCustom>
<TextCustom style={styles.time}>
{new Date(item.time).toLocaleTimeString([], {
hour: "2-digit",
minute: "2-digit",
})}
</TextCustom>
</View>
</View>
))}
{/* <TextInputCustom placeholder="Ketik pesan..." />
<Spacing/> */}
</ViewWrapper>
</> </>
); );
} }
const dummyData = [
{
nama: "Dina",
role: 1,
chat: "Hai! Kamu udah lihat dokumen proyek yang baru?",
time: "2025-07-24T09:01:15Z",
},
{
nama: "Rafi",
role: 2,
chat: "Halo! Iya, aku baru aja baca. Kayaknya kita harus revisi bagian akhir deh.",
time: "2025-07-24T09:02:03Z",
},
{
nama: "Dina",
role: 1,
chat: "Setuju. Aku juga kurang sreg sama penutupnya.",
time: "2025-07-24T09:02:45Z",
},
{
nama: "Rafi",
role: 2,
chat: "Oke, aku coba edit malam ini ya. Nanti aku share ulang versinya.",
time: "2025-07-24T09:03:10Z",
},
{
nama: "Dina",
role: 1,
chat: "Siap, makasih ya. Jangan begadang!",
time: "2025-07-24T09:03:30Z",
},
];
const styles = StyleSheet.create({
container: {
paddingVertical: 10,
paddingHorizontal: 12,
},
messageRow: {
flexDirection: "row",
marginBottom: 12,
},
rightAlign: {
justifyContent: "flex-end",
},
leftAlign: {
justifyContent: "flex-start",
},
bubble: {
maxWidth: "75%",
padding: 10,
borderRadius: 12,
},
bubbleRight: {
backgroundColor: "#DCF8C6", // hijau muda
borderTopRightRadius: 0,
},
bubbleLeft: {
backgroundColor: "#F0F0F0", // abu-abu terang
borderTopLeftRadius: 0,
},
sender: {
fontSize: 12,
fontWeight: "bold",
marginBottom: 2,
color: "#555",
},
message: {
fontSize: 15,
color: "#000",
},
time: {
fontSize: 10,
color: "#888",
textAlign: "right",
marginTop: 4,
},
});

View File

@@ -0,0 +1,80 @@
import {
AlertDefaultSystem,
ButtonCustom,
TextAreaCustom,
ViewWrapper,
} from "@/components";
import { useAuth } from "@/hooks/use-auth";
import { apiCollaborationCreatePartisipasi } from "@/service/api-client/api-collaboration";
import { router, useLocalSearchParams } from "expo-router";
import { useState } from "react";
import Toast from "react-native-toast-message";
export default function CollaborationCreatePartisipans() {
const { user } = useAuth();
const { id } = useLocalSearchParams();
const [description, setDescription] = useState("");
const [isLoading, setLoading] = useState(false);
const handlerSubmitParticipans = async () => {
try {
setLoading(true);
const response = await apiCollaborationCreatePartisipasi({
id: id as string,
data: {
authorId: user?.id,
description,
},
});
if (response.success) {
Toast.show({
type: "success",
text1: "Data berhasil disimpan",
});
router.replace(`/collaboration/${id}/list-of-participants`);
} else {
Toast.show({
type: "error",
text1: "Gagal menyimpan data",
});
}
} catch (error) {
console.log("[ERROR]", error);
} finally {
setLoading(false);
}
};
return (
<ViewWrapper>
<TextAreaCustom
// label="Deskripsi"
placeholder="Masukan deskripsi diri anda .."
value={description}
onChangeText={setDescription}
required
showCount
maxLength={1000}
/>
<ButtonCustom
disabled={description.length === 0}
isLoading={isLoading}
onPress={() => {
AlertDefaultSystem({
title: "Simpan data deskripsi",
message: "Apakah anda sudah yakin ingin menyimpan data ini ?",
textLeft: "Batal",
textRight: "Simpan",
onPressRight: () => {
handlerSubmitParticipans();
},
});
}}
>
Simpan
</ButtonCustom>
</ViewWrapper>
);
}

View File

@@ -1,26 +1,54 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { import {
AvatarUsernameAndOtherComponent, BackButton,
BaseBox, DotButton,
DrawerCustom, DrawerCustom,
StackCustom, MenuDrawerDynamicGrid,
TextCustom, ViewWrapper
ViewWrapper,
} from "@/components"; } from "@/components";
import { ICON_SIZE_SMALL } from "@/constants/constans-value";
import Collaboration_BoxDetailSection from "@/screens/Collaboration/BoxDetailSection"; import Collaboration_BoxDetailSection from "@/screens/Collaboration/BoxDetailSection";
import { MaterialIcons } from "@expo/vector-icons"; import { apiCollaborationGetOne } from "@/service/api-client/api-collaboration";
import { useLocalSearchParams } from "expo-router"; import { Ionicons } from "@expo/vector-icons";
import { useState } from "react"; import { router, Stack, useFocusEffect, useLocalSearchParams } from "expo-router";
import { useCallback, useState } from "react";
export default function CollaborationDetailParticipant() { export default function CollaborationDetailParticipant() {
const { id } = useLocalSearchParams(); const { id } = useLocalSearchParams();
const [openDrawerParticipant, setOpenDrawerParticipant] = useState(false); const [openDrawerParticipant, setOpenDrawerParticipant] = useState(false);
const [data, setData] = useState<any>();
useFocusEffect(
useCallback(() => {
onLoadData();
}, [id])
);
const onLoadData = async () => {
try {
const response = await apiCollaborationGetOne({ id: id as string });
if (response.success) {
setData(response.data);
}
} catch (error) {
console.log("[ERROR]", error);
}
};
return ( return (
<> <>
<Stack.Screen
options={{
title: "Detail Proyek",
headerLeft: () => <BackButton />,
headerRight: () => (
<DotButton onPress={() => setOpenDrawerParticipant(true)} />
),
}}
/>
<ViewWrapper> <ViewWrapper>
<Collaboration_BoxDetailSection id={id as string} /> <Collaboration_BoxDetailSection data={data} />
<BaseBox style={{ height: 500 }}> {/* <BaseBox style={{ height: 500 }}>
<TextCustom align="center" bold size="large"> <TextCustom align="center" bold size="large">
Partisipan Partisipan
</TextCustom> </TextCustom>
@@ -39,13 +67,33 @@ export default function CollaborationDetailParticipant() {
} }
/> />
))} ))}
</BaseBox> </BaseBox> */}
</ViewWrapper> </ViewWrapper>
<DrawerCustom <DrawerCustom
isVisible={openDrawerParticipant} isVisible={openDrawerParticipant}
closeDrawer={() => setOpenDrawerParticipant(false)} closeDrawer={() => setOpenDrawerParticipant(false)}
height={"auto"} height={"auto"}
>
<MenuDrawerDynamicGrid
data={[
{
icon: <Ionicons name="people" size={24} color="white" />,
label: "Daftar Partisipan",
path: `/collaboration/${id}/list-of-participants`,
},
]}
onPressItem={(item) => {
router.push(item.path as any);
setOpenDrawerParticipant(false);
}}
/>
</DrawerCustom>
{/* <DrawerCustom
isVisible={openDrawerParticipant}
closeDrawer={() => setOpenDrawerParticipant(false)}
height={"auto"}
> >
<StackCustom> <StackCustom>
<TextCustom bold>Deskripsi Diri</TextCustom> <TextCustom bold>Deskripsi Diri</TextCustom>
@@ -56,7 +104,7 @@ export default function CollaborationDetailParticipant() {
Temporibus iusto soluta necessitatibus. Temporibus iusto soluta necessitatibus.
</TextCustom> </TextCustom>
</StackCustom> </StackCustom>
</DrawerCustom> </DrawerCustom> */}
</> </>
); );
} }

View File

@@ -1,30 +1,65 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { import {
AlertDefaultSystem,
BackButton, BackButton,
ButtonCustom,
DotButton, DotButton,
DrawerCustom, DrawerCustom,
LoaderCustom,
MenuDrawerDynamicGrid, MenuDrawerDynamicGrid,
Spacing, Spacing,
StackCustom, ViewWrapper
TextCustom,
ViewWrapper,
} from "@/components"; } from "@/components";
import { IconEdit } from "@/components/_Icon"; import { IconEdit } from "@/components/_Icon";
import Collaboration_BoxDetailSection from "@/screens/Collaboration/BoxDetailSection"; import Collaboration_BoxDetailSection from "@/screens/Collaboration/BoxDetailSection";
import Collaboration_MainParticipanSelectedSection from "@/screens/Collaboration/ProjectMainSelectedSection"; import {
import { router, Stack, useLocalSearchParams } from "expo-router"; apiCollaborationGetOne
import { useState } from "react"; } from "@/service/api-client/api-collaboration";
import { MaterialIcons } from "@expo/vector-icons";
import {
router,
Stack,
useFocusEffect,
useLocalSearchParams,
} from "expo-router";
import { useCallback, useState } from "react";
export default function CollaborationDetailProjectMain() { export default function CollaborationDetailProjectMain() {
const { id } = useLocalSearchParams(); const { id } = useLocalSearchParams();
const [openDrawer, setOpenDrawer] = useState(false); const [openDrawer, setOpenDrawer] = useState(false);
const [openDrawerParticipant, setOpenDrawerParticipant] = useState(false); const [data, setData] = useState<any>();
const [selected, setSelected] = useState<(string | number)[]>([]); const [loadingGetData, setLoadingGetData] = useState(false);
const handleEdit = () => { useFocusEffect(
console.log("Edit collaboration"); useCallback(() => {
router.push("/(application)/(user)/collaboration/(id)/edit"); handlerLoadData();
}, [id])
);
const handlerLoadData = async () => {
try {
setLoadingGetData(true);
await onLoadData();
} catch (error) {
console.log("[ERROR]", error);
} finally {
setLoadingGetData(false);
}
};
const onLoadData = async () => {
try {
const response = await apiCollaborationGetOne({ id: id as string });
if (response.success) {
setData(response.data);
}
} catch (error) {
console.log("[ERROR]", error);
}
};
const handleSubmit = (item: any) => {
console.log("item :", item);
router.push(item.path);
setOpenDrawer(false);
}; };
return ( return (
@@ -37,34 +72,21 @@ export default function CollaborationDetailProjectMain() {
}} }}
/> />
<ViewWrapper> <ViewWrapper>
<Collaboration_BoxDetailSection id={id as string} /> {loadingGetData ? (
<Collaboration_MainParticipanSelectedSection <LoaderCustom />
selected={selected} ) : (
setSelected={setSelected} <>
setOpenDrawerParticipant={setOpenDrawerParticipant} <Collaboration_BoxDetailSection data={data} />
/> {/* <Collaboration_MainParticipanSelectedSection
selected={selected}
setSelected={setSelected}
setOpenDrawerParticipant={setOpenDrawerParticipant}
listData={listData as any}
/> */}
<ButtonCustom <Spacing />
onPress={() => { </>
AlertDefaultSystem({ )}
title: "Buat Grup",
message:
"Apakah anda yakin ingin membuat grup untuk proyek ini ?",
textLeft: "Tidak",
textRight: "Ya",
onPressLeft: () => {},
onPressRight: () => {
router.navigate(
"/(application)/(user)/collaboration/(tabs)/group"
);
console.log("selected :", selected);
},
});
}}
>
Buat Grup
</ButtonCustom>
<Spacing />
</ViewWrapper> </ViewWrapper>
<DrawerCustom <DrawerCustom
@@ -76,31 +98,20 @@ export default function CollaborationDetailProjectMain() {
data={[ data={[
{ {
label: "Edit", label: "Edit",
path: "/(application)/(user)/collaboration/(tabs)/group", path: `/(application)/(user)/collaboration/${id}/edit`,
icon: <IconEdit />, icon: <IconEdit />,
}, },
{
label: "Pilih Partisipan",
path: `/(application)/(user)/collaboration/${id}/select-of-participants`,
icon: <MaterialIcons name="checklist" size={24} color="white" />,
},
]} ]}
onPressItem={(item) => { onPressItem={(item: any) => {
handleEdit(); handleSubmit(item);
}} }}
/> />
</DrawerCustom> </DrawerCustom>
<DrawerCustom
isVisible={openDrawerParticipant}
closeDrawer={() => setOpenDrawerParticipant(false)}
height={"auto"}
>
<StackCustom>
<TextCustom bold>Deskripsi Diri</TextCustom>
<TextCustom>
Lorem ipsum dolor sit, amet consectetur adipisicing elit. Commodi,
itaque adipisci. Voluptas, sed quod! Ad facere labore voluptates,
neque quidem aut reprehenderit ducimus mollitia quisquam temporibus!
Temporibus iusto soluta necessitatibus.
</TextCustom>
</StackCustom>
</DrawerCustom>
</> </>
); );
} }

View File

@@ -1,53 +1,170 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { import {
ButtonCustom, ButtonCustom,
LoaderCustom,
SelectCustom, SelectCustom,
StackCustom, StackCustom,
TextAreaCustom, TextAreaCustom,
TextInputCustom, TextInputCustom,
ViewWrapper, ViewWrapper,
} from "@/components"; } from "@/components";
import { router } from "expo-router"; import {
apiCollaborationEditData,
apiCollaborationGetOne,
} from "@/service/api-client/api-collaboration";
import { apiMasterCollaborationType } from "@/service/api-client/api-master";
import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
import { useCallback, useState } from "react";
import Toast from "react-native-toast-message";
export default function CollaborationEdit() { export default function CollaborationEdit() {
const { id } = useLocalSearchParams();
const [data, setData] = useState<any>();
const [listMaster, setListMaster] = useState<any[]>([]);
const [loadingData, setLoadingData] = useState(false);
const [isLoading, setIsLoading] = useState(false);
useFocusEffect(
useCallback(() => {
const fetchData = async () => {
try {
setLoadingData(true);
await onLoadData();
await onLoadMaster();
} catch (error) {
console.log("[ERROR]", error);
} finally {
setLoadingData(false);
}
};
fetchData();
}, [id])
);
const onLoadData = async () => {
try {
const response = await apiCollaborationGetOne({ id: id as string });
if (response.success) {
setData(response.data);
}
} catch (error) {
console.log("[ERROR]", error);
}
};
async function onLoadMaster() {
try {
const response = await apiMasterCollaborationType();
setListMaster(response.data);
} catch (error) {
console.log("[ERROR]", error);
}
}
const handlerSubmitUpdate = async () => {
if (
!data?.title ||
!data?.lokasi ||
!data?.projectCollaborationMaster_IndustriId ||
!data?.purpose ||
!data?.benefit
) {
Toast.show({
type: "error",
text1: "Gagal",
text2: "Harap isi semua data",
});
return;
}
try {
setIsLoading(true);
const response = await apiCollaborationEditData({
id: id as string,
data: data,
});
if (response.success) {
Toast.show({
type: "success",
text1: response.message,
});
router.back();
} else {
Toast.show({
type: "error",
text1: response.message,
});
}
} catch (error) {
console.log("[ERROR]", error);
} finally {
setIsLoading(false);
}
};
return ( return (
<ViewWrapper> <ViewWrapper>
<StackCustom gap={"xs"}> {loadingData ? (
<TextInputCustom label="Judul" placeholder="Masukan judul" required /> <LoaderCustom />
<TextInputCustom label="Lokasi" placeholder="Masukan lokasi" required /> ) : (
<SelectCustom <StackCustom gap={"xs"}>
label="Pilih Industri" <TextInputCustom
data={[ label="Judul"
{ label: "Industri 1", value: "industri-1" }, placeholder="Masukan judul"
{ label: "Industri 2", value: "industri-2" }, required
{ label: "Industri 3", value: "industri-3" }, value={data?.title}
]} onChangeText={(value) => setData({ ...data, title: value })}
onChange={(value) => console.log(value)} />
/> <TextInputCustom
label="Lokasi"
placeholder="Masukan lokasi"
required
value={data?.lokasi}
onChangeText={(value) => setData({ ...data, lokasi: value })}
/>
<SelectCustom
label="Pilih Industri"
data={listMaster?.map((item: any) => ({
label: item.name,
value: item.id,
}))}
value={data?.projectCollaborationMaster_IndustriId}
onChange={(value) =>
setData({ ...data, projectCollaborationMaster_IndustriId: value })
}
/>
<TextAreaCustom <TextAreaCustom
required required
label="Tujuan Proyek" label="Tujuan Proyek"
placeholder="Masukan tujuan proyek" placeholder="Masukan tujuan proyek"
showCount showCount
maxLength={1000} maxLength={1000}
/> value={data?.purpose}
onChangeText={(value) => setData({ ...data, purpose: value })}
/>
<TextAreaCustom <TextAreaCustom
required required
label="Keuntungan Proyek" label="Keuntungan Proyek"
placeholder="Masukan keuntungan proyek" placeholder="Masukan keuntungan proyek"
showCount showCount
maxLength={1000} maxLength={1000}
/> value={data?.benefit}
onChangeText={(value) => setData({ ...data, benefit: value })}
/>
<ButtonCustom <ButtonCustom
title="Update" isLoading={isLoading}
onPress={() => { title="Update"
console.log("Update proyek"); onPress={() => {
router.back(); handlerSubmitUpdate();
}} }}
/> />
</StackCustom> </StackCustom>
)}
</ViewWrapper> </ViewWrapper>
); );
} }

View File

@@ -1,22 +1,75 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { import {
AlertDefaultSystem,
BackButton, BackButton,
ButtonCustom, ButtonCustom,
DotButton, DotButton,
DrawerCustom, DrawerCustom,
InformationBox,
LoaderCustom,
MenuDrawerDynamicGrid, MenuDrawerDynamicGrid,
TextAreaCustom,
ViewWrapper, ViewWrapper,
} from "@/components"; } from "@/components";
import { useAuth } from "@/hooks/use-auth";
import Collaboration_BoxDetailSection from "@/screens/Collaboration/BoxDetailSection"; import Collaboration_BoxDetailSection from "@/screens/Collaboration/BoxDetailSection";
import {
apiCollaborationGetOne,
apiCollaborationGetParticipants,
} from "@/service/api-client/api-collaboration";
import { Ionicons } from "@expo/vector-icons"; import { 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 { useCallback, useState } from "react";
export default function CollaborationDetail() { export default function CollaborationDetail() {
const { user } = useAuth();
const { id } = useLocalSearchParams(); const { id } = useLocalSearchParams();
const [openDrawerPartisipasi, setOpenDrawerPartisipasi] = useState(false); const [data, setData] = useState<any>();
const [openDrawerMenu, setOpenDrawerMenu] = useState(false); const [openDrawerMenu, setOpenDrawerMenu] = useState(false);
const [isParticipant, setIsParticipant] = useState(false);
const [loadingIsParticipant, setLoadingIsParticipant] = useState(false);
useFocusEffect(
useCallback(() => {
onLoadData();
onLoadParticipants();
}, [id])
);
const onLoadData = async () => {
try {
const response = await apiCollaborationGetOne({ id: id as string });
if (response.success) {
setData(response.data);
}
} catch (error) {
console.log("[ERROR]", error);
}
};
const onLoadParticipants = async () => {
try {
setLoadingIsParticipant(true);
const response = await apiCollaborationGetParticipants({
category: "check-participant",
id: id as string,
authorId: user?.id,
});
if (response.success) {
setIsParticipant(response.data);
}
} catch (error) {
console.log("[ERROR]", error);
} finally {
setLoadingIsParticipant(false);
}
};
return ( return (
<> <>
<Stack.Screen <Stack.Screen
@@ -29,15 +82,35 @@ export default function CollaborationDetail() {
}} }}
/> />
<ViewWrapper> <ViewWrapper>
<Collaboration_BoxDetailSection id={id as string} /> {!data && !isParticipant ? (
<LoaderCustom />
<ButtonCustom onPress={() => setOpenDrawerPartisipasi(true)}> ) : (
Partisipasi <>
</ButtonCustom> {user?.id === data?.Author?.id && (
<InformationBox
text={
"Tombol partisipasi hanya muncul pada proyek milik orang lain"
}
/>
)}
<Collaboration_BoxDetailSection data={data} />
{user?.id !== data?.Author?.id && (
<ButtonCustom
disabled={isParticipant || loadingIsParticipant}
onPress={() => {
router.push(`/collaboration/${id}/create-pacticipants`);
// setOpenDrawerPartisipasi(true);
}}
>
{isParticipant ? "Anda telah berpartisipasi" : "Partisipasi"}
</ButtonCustom>
)}
</>
)}
</ViewWrapper> </ViewWrapper>
{/* Drawer Partisipasi */} {/* Drawer Partisipasi */}
<DrawerCustom {/* <DrawerCustom
isVisible={openDrawerPartisipasi} isVisible={openDrawerPartisipasi}
closeDrawer={() => setOpenDrawerPartisipasi(false)} closeDrawer={() => setOpenDrawerPartisipasi(false)}
height={300} height={300}
@@ -48,6 +121,8 @@ export default function CollaborationDetail() {
required required
showCount showCount
maxLength={500} maxLength={500}
value={description}
onChangeText={setDescription}
/> />
<ButtonCustom <ButtonCustom
@@ -58,19 +133,21 @@ export default function CollaborationDetail() {
message: "Apakah anda sudah yakin ingin menyimpan data ini ?", message: "Apakah anda sudah yakin ingin menyimpan data ini ?",
textLeft: "Batal", textLeft: "Batal",
textRight: "Simpan", textRight: "Simpan",
onPressRight: () => router.replace(`/collaboration/(tabs)/group`), onPressRight: () => {
handlerSubmitParticipans();
},
}); });
}} }}
> >
Simpan Simpan
</ButtonCustom> </ButtonCustom>
</DrawerCustom> </DrawerCustom> */}
{/* Drawer Menu */} {/* Drawer Menu */}
<DrawerCustom <DrawerCustom
isVisible={openDrawerMenu} isVisible={openDrawerMenu}
closeDrawer={() => setOpenDrawerMenu(false)} closeDrawer={() => setOpenDrawerMenu(false)}
height={250} height={"auto"}
> >
<MenuDrawerDynamicGrid <MenuDrawerDynamicGrid
data={[ data={[

View File

@@ -1,38 +1,79 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { import {
AvatarUsernameAndOtherComponent, AvatarUsernameAndOtherComponent,
BaseBox, BaseBox,
DrawerCustom, DrawerCustom,
Spacing, LoaderCustom,
StackCustom, Spacing,
TextCustom, StackCustom,
ViewWrapper TextCustom,
ViewWrapper,
} from "@/components"; } from "@/components";
import { apiCollaborationGetParticipants } from "@/service/api-client/api-collaboration";
import { Feather } from "@expo/vector-icons"; import { Feather } from "@expo/vector-icons";
import { useLocalSearchParams } from "expo-router"; import { useLocalSearchParams } from "expo-router";
import { useState } from "react"; import _ from "lodash";
import { useEffect, useState } from "react";
import { ScrollView } from "react-native"; import { ScrollView } from "react-native";
export default function CollaborationListOfParticipants() { export default function CollaborationListOfParticipants() {
const { id } = useLocalSearchParams(); const { id } = useLocalSearchParams();
const [listData, setListData] = useState<any[]>();
const [loadingGetData, setLoadingGetData] = useState(false);
const [description, setDescription] = useState("");
useEffect(() => {
onLoadData();
}, [id]);
const onLoadData = async () => {
try {
setLoadingGetData(true);
const response = await apiCollaborationGetParticipants({
category: "list",
id: id as string,
});
if (response.success) {
setListData(response.data);
}
} catch (error) {
console.log("[ERROR]", error);
} finally {
setLoadingGetData(false);
}
};
const [openDrawer, setOpenDrawer] = useState(false); const [openDrawer, setOpenDrawer] = useState(false);
return ( return (
<> <>
<ViewWrapper> <ViewWrapper>
{Array.from({ length: 10 }).map((_, index) => ( {loadingGetData ? (
<BaseBox key={index} paddingBlock={5}> <LoaderCustom />
<AvatarUsernameAndOtherComponent ) : _.isEmpty(listData) ? (
avatarHref={`/profile/${id}`} <TextCustom align="center">Tidak ada partisipan</TextCustom>
rightComponent={ ) : (
<Feather listData?.map((item: any, index: number) => (
name="chevron-right" <BaseBox key={index} paddingBlock={5}>
size={24} <AvatarUsernameAndOtherComponent
color="white" avatar={item?.User?.Profile?.imageId}
onPress={() => setOpenDrawer(true)} avatarHref={`/profile/${item?.User?.Profile?.id}`}
/> name={item?.User?.username}
} rightComponent={
/> <Feather
</BaseBox> name="chevron-right"
))} size={24}
color="white"
onPress={() => {
setDescription(item?.deskripsi_diri);
setOpenDrawer(true);
}}
/>
}
/>
</BaseBox>
))
)}
</ViewWrapper> </ViewWrapper>
{/* Drawer */} {/* Drawer */}
@@ -44,34 +85,7 @@ export default function CollaborationListOfParticipants() {
<TextCustom bold>Deskripsi diri</TextCustom> <TextCustom bold>Deskripsi diri</TextCustom>
<BaseBox> <BaseBox>
<ScrollView style={{ height: "80%" }}> <ScrollView style={{ height: "80%" }}>
<TextCustom> <TextCustom>{description}</TextCustom>
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
eiusmod tempor incididunt ut labore et dolore magna aliqua.
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
eiusmod tempor incididunt ut labore et dolore magna aliqua.Lorem
ipsum dolor sit amet, consectetur adipiscing elit, sed do
eiusmod tempor incididunt ut labore et dolore magna aliqua.Lorem
ipsum dolor sit amet, consectetur adipiscing elit, sed do
eiusmod tempor incididunt ut labore et dolore magna aliqua.Lorem
ipsum dolor sit amet, consectetur adipiscing elit, sed do
eiusmod tempor incididunt ut iqua.Lorem ipsum dolor sit amet,
consectetur adipiscing elit, sed do eiusmod tempor incididunt ut
labore et dolore magna aliqua.Lorem ipsum dolor sit amet,
consectetur adipiscing elit, sed do eiusmod tempor incididunt ut
labore et dolore magna aliqua.Lorem ipsum dolor sit amet,
consectetur adipiscing elit, sed do eiusmod tempor incididunt ut
labore et dolore magna aliqua.Lorem ipsum dolor sit amet,
consectetur adipiscing elit, sed do eiusmod tempor incididunt ut
labore et dolore magna aliqua.Lorem ipsum dolor sit amet,
consectetur adipiscing elit, sed do eiusmod tempor incididunt ut
labore et dolore magna aliqua.Lorem ipsum dolor sit amet,
consectetur adipiscing elit, sed do eiusmod tempor incididunt ut
labore et dolore magna aliqua.Lorem ipsum dolor sit amet,
consectetur adipiscing elit, sed do eiusmod tempor incididunt ut
labore et dolore magna aliqua.Lorem ipsum dolor sit amet,
consectetur adipiscing elit, sed do eiusmod tempor incididunt ut
labore et dolore magna aliqua.
</TextCustom>
</ScrollView> </ScrollView>
</BaseBox> </BaseBox>
<Spacing /> <Spacing />

View File

@@ -0,0 +1,251 @@
/* eslint-disable react-hooks/exhaustive-deps */
import {
AlertDefaultSystem,
AvatarComp,
BaseBox,
BoxButtonOnFooter,
ButtonCustom,
CheckboxCustom,
CheckboxGroup,
DrawerCustom,
Grid,
LoaderCustom,
Spacing,
StackCustom,
TextAreaCustom,
TextCustom,
ViewWrapper,
} from "@/components";
import ModalCustom from "@/components/Modal/ModalCustom";
import { ICON_SIZE_SMALL } from "@/constants/constans-value";
import { useAuth } from "@/hooks/use-auth";
import {
apiCollaborationCreateGroup,
apiCollaborationGetParticipants,
} from "@/service/api-client/api-collaboration";
import { MaterialIcons } from "@expo/vector-icons";
import { router, useLocalSearchParams } from "expo-router";
import _ from "lodash";
import { useEffect, useState } from "react";
import { ScrollView, View } from "react-native";
import Toast from "react-native-toast-message";
export default function CollaborationSelectOfParticipants() {
const { user } = useAuth();
const { id } = useLocalSearchParams();
const [listData, setListData] = useState<any[]>();
const [loadingGetData, setLoadingGetData] = useState(false);
const [description, setDescription] = useState("");
const [selected, setSelected] = useState<(string | number)[]>([]);
const [openDrawer, setOpenDrawer] = useState(false);
const [nameGroup, setNameGroup] = useState("");
const [openModal, setOpenModal] = useState(false);
const [isLoading, setIsLoading] = useState(false);
useEffect(() => {
onLoadData();
}, [id]);
const onLoadData = async () => {
try {
setLoadingGetData(true);
const response = await apiCollaborationGetParticipants({
category: "list",
id: id as string,
});
// console.log("response :", JSON.stringify(response.data, null, 2));
if (response.success) {
setListData(response.data);
}
} catch (error) {
console.log("[ERROR]", error);
} finally {
setLoadingGetData(false);
}
};
const handlerCreateGroup = async () => {
if (_.isEmpty(nameGroup)) {
Toast.show({
type: "error",
text1: "Nama grup tidak boleh kosong",
});
return;
}
try {
setIsLoading(true);
const response = await apiCollaborationCreateGroup({
id: id as string,
data: {
authorId: user?.id,
nameGroup: nameGroup,
listSelect: selected,
},
});
if (response.success) {
Toast.show({
type: "success",
text1: "Grup berhasil dibuat",
});
router.push("/(application)/(user)/collaboration/(tabs)/group");
} else {
Toast.show({
type: "error",
text1: "Gagal membuat grup",
});
}
} catch (error) {
console.log("[ERROR]", error);
} finally {
setIsLoading(false);
}
};
const handlerSubmit = () => {
return (
<>
<BoxButtonOnFooter>
<ButtonCustom
disabled={_.isEmpty(selected)}
onPress={() => {
setOpenModal(true);
setNameGroup("");
}}
>
Buat Grup
</ButtonCustom>
</BoxButtonOnFooter>
</>
);
};
return (
<>
<ViewWrapper
footerComponent={_.isEmpty(listData) ? null : handlerSubmit()}
>
{loadingGetData ? (
<LoaderCustom />
) : _.isEmpty(listData) ? (
<TextCustom align="center">Tidak ada partisipan</TextCustom>
) : (
<StackCustom>
<TextCustom size="default" color="red" bold>
*{" "}
<TextCustom size="small" semiBold>
Pilih user yang akan menjadi tim proyek anda
</TextCustom>
</TextCustom>
<CheckboxGroup
value={selected}
onChange={(val: any) => {
console.log("val :", val);
setSelected(val);
}}
>
{listData?.map((item: any, index: any) => (
<View key={index}>
<Grid key={index}>
<Grid.Col
span={2}
style={{ alignItems: "center", justifyContent: "center" }}
>
<CheckboxCustom valueKey={item?.User?.id} />
</Grid.Col>
<Grid.Col span={2} style={{ alignItems: "center" }}>
<AvatarComp
size="base"
fileId={item?.User?.Profile?.imageId}
/>
</Grid.Col>
<Grid.Col span={6} style={{ justifyContent: "center" }}>
<TextCustom bold truncate>
{item?.User?.username}
</TextCustom>
</Grid.Col>
<Grid.Col
span={2}
style={{ alignItems: "center", justifyContent: "center" }}
>
<MaterialIcons
name="notes"
size={ICON_SIZE_SMALL}
color="white"
onPress={() => {
setOpenDrawer(true);
setDescription(item?.deskripsi_diri);
}}
/>
</Grid.Col>
</Grid>
</View>
))}
</CheckboxGroup>
</StackCustom>
)}
</ViewWrapper>
<ModalCustom isVisible={openModal}>
<StackCustom gap={0}>
<TextAreaCustom
placeholder="Nama Grup"
value={nameGroup}
onChangeText={(val) => setNameGroup(val)}
/>
<Grid>
<Grid.Col span={6} style={{ paddingRight: 10 }}>
<ButtonCustom
backgroundColor="gray"
onPress={() => {
setOpenModal(false);
}}
>
Batal
</ButtonCustom>
</Grid.Col>
<Grid.Col span={6} style={{ paddingLeft: 10 }}>
<ButtonCustom
isLoading={isLoading}
disabled={_.isEmpty(nameGroup)}
onPress={() => {
AlertDefaultSystem({
title: "Buat Grup",
message:
"Apakah anda yakin ingin membuat grup untuk proyek ini ?",
textLeft: "Tidak",
textRight: "Ya",
onPressLeft: () => {},
onPressRight: () => {
handlerCreateGroup();
},
});
}}
>
Simpan
</ButtonCustom>
</Grid.Col>
</Grid>
</StackCustom>
</ModalCustom>
{/* Drawer */}
<DrawerCustom
isVisible={openDrawer}
closeDrawer={() => setOpenDrawer(false)}
>
<StackCustom>
<TextCustom bold>Deskripsi diri</TextCustom>
<BaseBox>
<ScrollView style={{ height: "80%" }}>
<TextCustom>{description}</TextCustom>
</ScrollView>
</BaseBox>
<Spacing />
</StackCustom>
</DrawerCustom>
</>
);
}

View File

@@ -1,53 +1,174 @@
import { import {
ButtonCustom, ButtonCustom,
SelectCustom, LoaderCustom,
StackCustom, SelectCustom,
TextAreaCustom, StackCustom,
TextInputCustom, TextAreaCustom,
ViewWrapper TextInputCustom,
ViewWrapper,
} from "@/components"; } from "@/components";
import { useAuth } from "@/hooks/use-auth";
import { apiCollaborationCreate } from "@/service/api-client/api-collaboration";
import { apiMasterCollaborationType } from "@/service/api-client/api-master";
import { router } from "expo-router"; import { router } from "expo-router";
import React, { useEffect, useState } from "react";
import Toast from "react-native-toast-message";
interface CollaborationCreateProps {
title?: string;
lokasi?: string;
purpose?: string;
benefit?: string;
projectCollaborationMaster_IndustriId?: string;
userId?: string;
}
export default function CollaborationCreate() { export default function CollaborationCreate() {
const { user } = useAuth();
const [listMaster, setListMaster] = useState<any>([]);
const [loadingMaster, setLoadingMaster] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const [data, setData] = React.useState<CollaborationCreateProps>({
title: "",
lokasi: "",
purpose: "",
benefit: "",
projectCollaborationMaster_IndustriId: "",
userId: "",
});
useEffect(() => {
onLoadMaster();
}, []);
async function onLoadMaster() {
try {
setLoadingMaster(true);
const response = await apiMasterCollaborationType();
setListMaster(response.data);
} catch (error) {
console.log("[ERROR]", error);
} finally {
setLoadingMaster(false);
}
}
const handlerSubmit = async () => {
if (
!data?.title ||
!data?.lokasi ||
!data?.purpose ||
!data?.benefit ||
!data?.projectCollaborationMaster_IndustriId
) {
Toast.show({
type: "error",
text1: "Gagal",
text2: "Harap isi semua data",
});
return;
}
const newData: CollaborationCreateProps = {
title: data?.title,
lokasi: data?.lokasi,
purpose: data?.purpose,
benefit: data?.benefit,
projectCollaborationMaster_IndustriId:
data?.projectCollaborationMaster_IndustriId,
userId: user?.id,
};
try {
setIsLoading(true);
const response = await apiCollaborationCreate({ data: newData });
if (response.success) {
Toast.show({
type: "success",
text1: "Berhasil",
text2: response.message,
});
router.back();
} else {
Toast.show({
type: "error",
text1: "Gagal",
text2: response.message,
});
}
} catch (error) {
console.log("[ERROR]", error);
} finally {
setIsLoading(false);
}
};
return ( return (
<ViewWrapper> <ViewWrapper>
<StackCustom gap={"xs"}> {loadingMaster ? (
<TextInputCustom label="Judul" placeholder="Masukan judul" required /> <LoaderCustom />
<TextInputCustom label="Lokasi" placeholder="Masukan lokasi" required /> ) : (
<SelectCustom <StackCustom gap={"xs"}>
label="Pilih Industri" <TextInputCustom
data={[ label="Judul"
{ label: "Industri 1", value: "industri-1" }, placeholder="Masukan judul"
{ label: "Industri 2", value: "industri-2" }, required
{ label: "Industri 3", value: "industri-3" }, value={data?.title}
]} onChangeText={(value: any) => setData({ ...data, title: value })}
onChange={(value) => console.log(value)} />
/>
<TextAreaCustom <TextInputCustom
required label="Lokasi"
label="Tujuan Proyek" placeholder="Masukan lokasi"
placeholder="Masukan tujuan proyek" required
showCount value={data?.lokasi}
maxLength={1000} onChangeText={(value: any) => setData({ ...data, lokasi: value })}
/> />
<TextAreaCustom <SelectCustom
required label="Pilih Industri"
label="Keuntungan Proyek" data={listMaster?.map((item: any) => ({
placeholder="Masukan keuntungan proyek" label: item.name,
showCount value: item.id,
maxLength={1000} }))}
/> value={data?.projectCollaborationMaster_IndustriId}
onChange={(value: any) => {
console.log(value);
setData({
...data,
projectCollaborationMaster_IndustriId: value,
});
}}
/>
<ButtonCustom <TextAreaCustom
title="Simpan" required
onPress={() => { label="Tujuan Proyek"
console.log("Simpan proyek"); placeholder="Masukan tujuan proyek"
router.back(); showCount
}} maxLength={1000}
/> value={data?.purpose}
</StackCustom> onChangeText={(value: any) => setData({ ...data, purpose: value })}
/>
<TextAreaCustom
required
label="Keuntungan Proyek"
placeholder="Masukan keuntungan proyek"
showCount
maxLength={1000}
value={data?.benefit}
onChangeText={(value: any) => setData({ ...data, benefit: value })}
/>
<ButtonCustom
isLoading={isLoading}
title="Simpan"
onPress={() => handlerSubmit()}
/>
</StackCustom>
)}
</ViewWrapper> </ViewWrapper>
); );
} }

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?.MasterBank?.norek}
</TextCustom>
</Grid.Col>
<Grid.Col
span={4}
style={{
alignItems: "flex-end",
}}
>
<CopyButton textToCopy={data?.MasterBank?.norek} />
</Grid.Col>
</Grid>
</BaseBox>
</StackCustom>
</BaseBox>
<BaseBox>
<StackCustom gap={"xs"}>
<TextCustom>Jumlah Transaksi</TextCustom>
<Spacing height={10} />
<BaseBox backgroundColor={MainColor.soft_darkblue}>
<Grid containerStyle={{ justifyContent: "center" }}>
<Grid.Col
span={8}
style={{
justifyContent: "center",
}}
>
<TextCustom size="xlarge" bold color="yellow">
Rp. {formatCurrencyDisplay(data?.nominal) || "-"}
</TextCustom>
</Grid.Col>
<Grid.Col
span={4}
style={{
alignItems: "flex-end",
}}
>
<CopyButton textToCopy={data?.nominal} />
</Grid.Col>
</Grid>
</BaseBox>
</StackCustom>
</BaseBox>
<BaseBox>
<StackCustom>
<TextCustom bold align="center" size={"small"} color="gray">
Upload bukti transfer anda.
</TextCustom>
{image ? (
<View
style={{
flexDirection: "row",
alignItems: "center",
justifyContent: "center",
gap: 10,
paddingInline: 20,
}}
>
<TextCustom bold align="center" truncate>
{image?.name}
</TextCustom>
</View>
) : null}
<ButtonCenteredOnly
onPress={() => {
pickFile({
allowedType: "image",
setImageUri(file) {
setImage(file);
},
});
}}
icon="upload"
>
Upload
</ButtonCenteredOnly>
</StackCustom>
</BaseBox>
<ButtonCustom
disabled={!image}
isLoading={isLoading}
onPress={() => {
handlerUpdateInvoice();
}}
>
Simpan
</ButtonCustom>
</StackCustom>
<Spacing />
</ViewWrapper>
</>
);
}

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,17 +1,71 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { import {
BaseBox, BaseBox,
ButtonCenteredOnly, ButtonCenteredOnly,
Grid, Grid,
InformationBox, InformationBox,
LoaderCustom,
StackCustom, StackCustom,
TextCustom, TextCustom,
ViewWrapper, ViewWrapper,
} from "@/components"; } from "@/components";
import {
apiDonationDisbursementOfFundsListById,
apiDonationGetOne,
} from "@/service/api-client/api-donation";
import { formatCurrencyDisplay } from "@/utils/formatCurrencyDisplay";
import dayjs from "dayjs"; import dayjs from "dayjs";
import { router, useLocalSearchParams } from "expo-router"; import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
import _ from "lodash";
import React, { useState } from "react";
export default function DonationFundDisbursement() { export default function DonationFundDisbursement() {
const { id } = useLocalSearchParams(); const { id } = useLocalSearchParams();
const [data, setData] = useState({
totalPencairan: 0,
akumulasiPencairan: 0,
});
const [listData, setListData] = React.useState<any[] | null>(null);
const [loadData, setLoadData] = React.useState(false);
useFocusEffect(
React.useCallback(() => {
onLoadData();
}, [id])
);
const onLoadData = async () => {
try {
setLoadData(true);
const responseData = await apiDonationGetOne({
id: id as string,
category: "permanent",
});
if (responseData.success) {
setData({
totalPencairan: responseData.data.totalPencairan,
akumulasiPencairan: responseData.data.akumulasiPencairan,
});
}
const responseList = await apiDonationDisbursementOfFundsListById({
id: id as string,
});
if (responseList.success) {
setListData(responseList.data);
}
} catch (error) {
console.log("[ERROR]", error);
} finally {
setLoadData(false);
}
};
return ( return (
<> <>
<ViewWrapper> <ViewWrapper>
@@ -20,47 +74,50 @@ export default function DonationFundDisbursement() {
<Grid> <Grid>
<Grid.Col span={6}> <Grid.Col span={6}>
<TextCustom bold color="yellow"> <TextCustom bold color="yellow">
Rp. 0 Rp. {formatCurrencyDisplay(data?.totalPencairan)}
</TextCustom> </TextCustom>
<TextCustom size="small">Total Pencairan Dana</TextCustom> <TextCustom size="small">Total Pencairan Dana</TextCustom>
</Grid.Col> </Grid.Col>
<Grid.Col span={6}> <Grid.Col span={6}>
<TextCustom bold color="yellow"> <TextCustom bold color="yellow">
0 kali {data?.akumulasiPencairan} kali
</TextCustom> </TextCustom>
<TextCustom size="small">Akumulasi Pencairan</TextCustom> <TextCustom size="small">Akumulasi Pencairan</TextCustom>
</Grid.Col> </Grid.Col>
</Grid> </Grid>
</BaseBox> </BaseBox>
{Array.from({ length: 10 }).map((_, index) => ( {loadData ? (
<BaseBox key={index}> <LoaderCustom />
<StackCustom> ) : _.isEmpty(listData) ? (
<Grid> <TextCustom align="center" color="gray">
<Grid.Col span={8}> Belum ada data
<TextCustom bold>Pencairan ke - {index + 1}</TextCustom> </TextCustom>
</Grid.Col> ) : (
<Grid.Col span={4} style={{ alignItems: "flex-end" }}> listData?.map((item, index) => (
<TextCustom>{dayjs().format("DD MMM YYYY")}</TextCustom> <BaseBox key={index}>
</Grid.Col> <StackCustom>
</Grid> <Grid>
<TextCustom> <Grid.Col span={8}>
Lorem ipsum dolor sit amet consectetur adipisicing elit. <TextCustom bold>{item?.title}</TextCustom>
Nesciunt dolor ad sit? Eaque rem nihil natus, id, esse possimus </Grid.Col>
perferendis provident velit illo consectetur distinctio ab <Grid.Col span={4} style={{ alignItems: "flex-end" }}>
accusantium quis earum omnis! <TextCustom>{dayjs(item?.createdAt).format("DD MMM YYYY")}</TextCustom>
</TextCustom> </Grid.Col>
<ButtonCenteredOnly </Grid>
onPress={() => { <TextCustom>{item?.deskripsi}</TextCustom>
router.navigate(`/(application)/(file)/${id}`); <ButtonCenteredOnly
}} onPress={() => {
icon="file-text" router.navigate(`/(application)/(image)/preview-image/${item?.imageId}`);
> }}
Bukti Transaksi icon="file-text"
</ButtonCenteredOnly> >
</StackCustom> Bukti Transaksi
</BaseBox> </ButtonCenteredOnly>
))} </StackCustom>
</BaseBox>
))
)}
</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
bottomSection={<Donation_ProgressSection id={id as string} />} sisaHari={value.sisa}
reminder={value.reminder}
data={data}
bottomSection={<Donation_ProgressSection id={id as string} progres={Number(data?.progres) || 0} />}
/>
<Donation_ComponentInfoFundrising dataAuthor={data?.Author} />
<Donation_ComponentStoryFunrising
id={id as string}
dataStory={data?.CeritaDonasi}
/> />
<Donation_ComponentInfoFundrising id={id as string} />
<Donation_ComponentStoryFunrising id={id as string} />
</StackCustom> </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,46 +1,93 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { import {
BaseBox, BaseBox,
Grid, Grid,
LoaderCustom,
Spacing,
StackCustom, StackCustom,
TextCustom, TextCustom,
ViewWrapper, ViewWrapper,
} from "@/components"; } from "@/components";
import { MainColor } from "@/constants/color-palet"; import { MainColor } from "@/constants/color-palet";
import { apiAdminDonationListOfDonaturById } from "@/service/api-admin/api-admin-donation";
import { formatCurrencyDisplay } from "@/utils/formatCurrencyDisplay";
import { FontAwesome6 } from "@expo/vector-icons"; import { FontAwesome6 } from "@expo/vector-icons";
import dayjs from "dayjs"; import dayjs from "dayjs";
import { useFocusEffect, useLocalSearchParams } from "expo-router";
import _ from "lodash";
import { useCallback, useState } from "react";
export default function Donation_ListOfDonatur() { export default function Donation_ListOfDonatur() {
const { id } = useLocalSearchParams();
const [listData, setListData] = useState<any[] | null>(null);
const [loadData, setLoadData] = useState(false);
useFocusEffect(
useCallback(() => {
onLoadData();
}, [id])
);
const onLoadData = async () => {
try {
setLoadData(true);
const response = await apiAdminDonationListOfDonaturById({
id: id as string,
});
if (response.success) {
setListData(response.data);
}
} catch (error) {
console.log("[ERROR]", error);
} finally {
setLoadData(false);
}
};
return ( return (
<> <>
<ViewWrapper> <ViewWrapper>
{Array.from({ length: 10 }).map((_, index) => ( {loadData ? (
<BaseBox key={index}> <LoaderCustom />
<Grid> ) : _.isEmpty(listData) ? (
<Grid.Col <TextCustom bold align="center">
span={3} Belum ada donatur
style={{ alignItems: "center", justifyContent: "center" }} </TextCustom>
> ) : (
<FontAwesome6 listData?.map((item: any, index: number) => (
name="face-smile-wink" <BaseBox key={index}>
size={50} <Grid>
style={{ color: MainColor.yellow }} <Grid.Col
/> span={3}
</Grid.Col> style={{ alignItems: "center", justifyContent: "center" }}
<Grid.Col span={9}> >
<StackCustom gap={"xs"}> <FontAwesome6
name="face-smile-wink"
size={50}
style={{ color: MainColor.yellow }}
/>
</Grid.Col>
<Grid.Col span={9}>
<TextCustom bold size="large"> <TextCustom bold size="large">
Username {item?.Author?.username || "-"}
</TextCustom> </TextCustom>
<TextCustom>Berdonas sebesar </TextCustom> <Spacing/>
<TextCustom bold size="large" color="yellow"> <StackCustom gap={"xs"}>
Rp. 100.000 <TextCustom size={"small"}>Berdonas sebesar </TextCustom>
</TextCustom> <TextCustom bold size="large" color="yellow">
<TextCustom>{dayjs().format("DD MMM YYYY")}</TextCustom> Rp. {formatCurrencyDisplay(item?.nominal)}
</StackCustom> </TextCustom>
</Grid.Col> <TextCustom>
</Grid> {dayjs(item?.createdAt).format("DD MMM YYYY, HH:mm")}
</BaseBox> </TextCustom>
))} </StackCustom>
</Grid.Col>
</Grid>
</BaseBox>
))
)}
</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,37 +1,98 @@
import { import {
BoxButtonOnFooter, BoxButtonOnFooter,
ButtonCustom, ButtonCustom,
LoaderCustom,
TextAreaCustom, TextAreaCustom,
ViewWrapper, ViewWrapper,
} from "@/components"; } from "@/components";
import { router } from "expo-router"; import { apiForumGetOne, apiForumUpdate } from "@/service/api-client/api-forum";
import { useState } from "react"; import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
import { useCallback, useState } from "react";
import Toast from "react-native-toast-message";
export default function ForumEdit() { export default function ForumEdit() {
const { id } = useLocalSearchParams();
const [text, setText] = useState(""); const [text, setText] = useState("");
const [loadingGetData, setLoadingGetData] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const buttonFooter = ( useFocusEffect(
<BoxButtonOnFooter> useCallback(() => {
<ButtonCustom onLoadData(id as string);
onPress={() => { }, [id])
console.log("Posting", text);
router.back();
}}
>
Update
</ButtonCustom>
</BoxButtonOnFooter>
); );
const onLoadData = async (id: string) => {
try {
setLoadingGetData(true);
const response = await apiForumGetOne({ id });
setText(response.data.diskusi);
} catch (error) {
console.log("[ERROR]", error);
} finally {
setLoadingGetData(false);
}
};
const handlerUpdateData = async () => {
if (!text) {
Toast.show({
type: "error",
text1: "Harap masukkan diskusi",
});
return;
}
try {
setIsLoading(true);
const response = await apiForumUpdate({
id: id as string,
data: text,
});
if (response.success) {
Toast.show({
type: "success",
text1: "Berhasil diupdate",
});
router.back();
}
} catch (error) {
console.log("[ERROR]", error);
} finally {
setIsLoading(false);
}
};
const buttonFooter = () => {
return (
<>
{!loadingGetData && (
<BoxButtonOnFooter>
<ButtonCustom isLoading={isLoading} onPress={handlerUpdateData}>
Update
</ButtonCustom>
</BoxButtonOnFooter>
)}
</>
);
};
return ( return (
<ViewWrapper footerComponent={buttonFooter}> <ViewWrapper footerComponent={buttonFooter()}>
<TextAreaCustom {!loadingGetData ? (
placeholder="Ketik diskusi anda..." <TextAreaCustom
maxLength={1000} placeholder="Ketik diskusi anda..."
showCount maxLength={1000}
value={text} showCount
onChangeText={setText} value={text}
/> onChangeText={(value) => {
setText(value);
}}
/>
) : (
<LoaderCustom />
)}
</ViewWrapper> </ViewWrapper>
); );
} }

View File

@@ -1,35 +1,86 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { import {
AlertCustom, AvatarComp,
AvatarCustom,
ButtonCustom, ButtonCustom,
CenterCustom, CenterCustom,
DrawerCustom, DrawerCustom,
FloatingButton,
Grid, Grid,
LoaderCustom,
StackCustom, StackCustom,
TextCustom, TextCustom,
ViewWrapper, ViewWrapper,
} from "@/components"; } from "@/components";
import { MainColor } from "@/constants/color-palet"; 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 { useLocalSearchParams } from "expo-router"; import { apiForumGetAll } from "@/service/api-client/api-forum";
import { useState } from "react"; import { apiUser } from "@/service/api-client/api-user";
import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
import _ from "lodash";
import { useCallback, useState } from "react";
export default function Forumku() { export default function Forumku() {
const { id } = useLocalSearchParams(); const { id } = useLocalSearchParams();
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 [listData, setListData] = useState<any | null>(null);
const [deleteAlert, setDeleteAlert] = useState(false); const [dataUser, setDataUser] = useState<any | null>(null);
const [loadingGetList, setLoadingGetList] = useState(false);
useFocusEffect(
useCallback(() => {
onLoadData();
onLoadDataProfile(id as string);
}, [id])
);
const onLoadDataProfile = async (id: string) => {
try {
const response = await apiUser(id);
setDataUser(response.data);
} catch (error) {
console.log("[ERROR]", error);
} finally {
}
};
const onLoadData = async () => {
try {
setLoadingGetList(true);
const response = await apiForumGetAll({
search: "",
authorId: id as string,
});
setListData(response.data);
} catch (error) {
console.log("[ERROR]", error);
} finally {
setLoadingGetList(false);
}
};
return ( return (
<> <>
<ViewWrapper> <ViewWrapper
floatingButton={
user?.id === id && (
<FloatingButton
onPress={() =>
router.navigate("/(application)/(user)/forum/create")
}
/>
)
}
>
<StackCustom> <StackCustom>
<CenterCustom> <CenterCustom>
<AvatarCustom <AvatarComp
href={`/(application)/(image)/preview-image/${id}`} fileId={dataUser?.Profile?.imageId}
href={`/(application)/(image)/preview-image/${dataUser?.Profile?.imageId}`}
size="xl" size="xl"
/> />
</CenterCustom> </CenterCustom>
@@ -37,32 +88,43 @@ export default function Forumku() {
<Grid> <Grid>
<Grid.Col span={6}> <Grid.Col span={6}>
<TextCustom bold truncate> <TextCustom bold truncate>
@bagas_banuna @{dataUser?.username || "-"}
</TextCustom> </TextCustom>
<TextCustom>1 postingan</TextCustom> <TextCustom>{listData?.length || "0"} postingan</TextCustom>
</Grid.Col> </Grid.Col>
<Grid.Col span={6} style={{ alignItems: "flex-end" }}> <Grid.Col span={6} style={{ alignItems: "flex-end" }}>
<ButtonCustom href={`/profile/${id}`}> <ButtonCustom href={`/profile/${dataUser?.Profile?.id}`}>
Kunjungi Profile Kunjungi Profile
</ButtonCustom> </ButtonCustom>
</Grid.Col> </Grid.Col>
</Grid> </Grid>
{listDummyDiscussionForum.map((e, i) => ( {loadingGetList ? (
<Forum_BoxDetailSection <LoaderCustom />
key={i} ) : _.isEmpty(listData) ? (
data={e} <TextCustom> Tidak ada diskusi</TextCustom>
setOpenDrawer={setOpenDrawer} ) : (
setStatus={setStatus} <>
isTruncate={true} {listData?.map((item: any, index: number) => (
href={`/forum/${id}`} <Forum_BoxDetailSection
/> isRightComponent={false}
))} key={index}
data={item}
isTruncate={true}
href={`/forum/${item.id}`}
onSetData={(value) => {
setOpenDrawer(value.setOpenDrawer);
setStatus(value.setStatus);
}}
/>
))}
</>
)}
</StackCustom> </StackCustom>
</ViewWrapper> </ViewWrapper>
{/* Drawer Komponen Eksternal */} {/* Drawer Komponen Eksternal */}
<DrawerCustom <DrawerCustom
height={350} height={"auto"}
isVisible={openDrawer} isVisible={openDrawer}
closeDrawer={() => setOpenDrawer(false)} closeDrawer={() => setOpenDrawer(false)}
> >
@@ -72,42 +134,9 @@ export default function Forumku() {
setIsDrawerOpen={() => { setIsDrawerOpen={() => {
setOpenDrawer(false); setOpenDrawer(false);
}} }}
setShowDeleteAlert={setDeleteAlert} authorId={id as string}
setShowAlertStatus={setAlertStatus}
/> />
</DrawerCustom> </DrawerCustom>
{/* Alert Komponen Eksternal */}
<AlertCustom
isVisible={alertStatus}
onLeftPress={() => setAlertStatus(false)}
onRightPress={() => {
setOpenDrawer(false);
setAlertStatus(false);
console.log("Ubah status forum");
}}
title="Ubah Status Forum"
message="Apakah Anda yakin ingin mengubah status forum ini?"
textLeft="Batal"
textRight="Ubah"
colorRight={MainColor.green}
/>
{/* Alert Delete */}
<AlertCustom
isVisible={deleteAlert}
onLeftPress={() => setDeleteAlert(false)}
onRightPress={() => {
setOpenDrawer(false);
setDeleteAlert(false);
console.log("Hapus forum");
}}
title="Hapus Forum"
message="Apakah Anda yakin ingin menghapus forum ini?"
textLeft="Batal"
textRight="Hapus"
colorRight={MainColor.red}
/>
</> </>
); );
} }

View File

@@ -1,176 +1,263 @@
import { import {
AlertCustom,
ButtonCustom, ButtonCustom,
DrawerCustom, DrawerCustom,
LoaderCustom,
Spacing, Spacing,
TextAreaCustom, TextAreaCustom,
TextCustom,
ViewWrapper, ViewWrapper,
} from "@/components"; } from "@/components";
import { MainColor } from "@/constants/color-palet"; import { useAuth } from "@/hooks/use-auth";
import Forum_CommentarBoxSection from "@/screens/Forum/CommentarBoxSection"; import Forum_CommentarBoxSection from "@/screens/Forum/CommentarBoxSection";
import Forum_BoxDetailSection from "@/screens/Forum/DiscussionBoxSection"; import Forum_BoxDetailSection from "@/screens/Forum/DiscussionBoxSection";
import { listDummyCommentarForum } 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 Forum_MenuDrawerCommentar from "@/screens/Forum/MenuDrawerSection.tsx/MenuCommentar"; import Forum_MenuDrawerCommentar from "@/screens/Forum/MenuDrawerSection.tsx/MenuCommentar";
import { router, useLocalSearchParams } from "expo-router"; import {
import { useState } from "react"; apiForumCreateComment,
apiForumGetComment,
apiForumGetOne,
apiForumUpdateStatus,
} from "@/service/api-client/api-forum";
import { useFocusEffect, useLocalSearchParams } from "expo-router";
import _ from "lodash";
import { useCallback, useEffect, useState } from "react";
interface CommentProps {
id: string;
isActive: boolean;
komentar: string;
createdAt: Date;
authorId: string;
Author: {
id: string;
username: string;
Profile: {
id: string;
imageId: string;
};
};
}
export default function ForumDetail() { export default function ForumDetail() {
const { id } = useLocalSearchParams(); const { id } = useLocalSearchParams();
console.log(id); const { user } = useAuth();
const [openDrawer, setOpenDrawer] = useState(false); const [openDrawer, setOpenDrawer] = useState(false);
const [data, setData] = useState<any | null>(null);
const [listComment, setListComment] = useState<CommentProps[] | null>(null);
const [isLoadingComment, setLoadingComment] = useState(false);
// Status
const [status, setStatus] = useState(""); const [status, setStatus] = useState("");
const [alertStatus, setAlertStatus] = useState(false);
const [deleteAlert, setDeleteAlert] = useState(false);
const [text, setText] = useState(""); const [text, setText] = useState("");
const [authorId, setAuthorId] = useState("");
const [dataId, setDataId] = useState("");
// Comentar // Comentar
const [openDrawerCommentar, setOpenDrawerCommentar] = useState(false); const [openDrawerCommentar, setOpenDrawerCommentar] = useState(false);
const [alertDeleteCommentar, setAlertDeleteCommentar] = useState(false); const [commentId, setCommentId] = useState("");
const [commentAuthorId, setCommentAuthorId] = useState("");
const dataDummy = { useFocusEffect(
name: "Bagas", useCallback(() => {
status: "Open", onLoadData(id as string);
date: "14/07/2025", }, [id])
deskripsi: );
"Lorem ipsum dolor sit amet consectetur adipisicing elit. Vitae inventore iure pariatur, libero omnis excepturi. Ullam ad officiis deleniti quos esse odit nesciunt, ipsam adipisci cumque aliquam corporis culpa fugit?",
jumlahBalas: 2, const onLoadData = async (id: string) => {
try {
const response = await apiForumGetOne({ id });
setData(response.data);
} catch (error) {
console.log("[ERROR]", error);
}
};
useEffect(() => {
onLoadListComment(id as string);
}, [id]);
const onLoadListComment = async (id: string) => {
try {
const response = await apiForumGetComment({
id: id as string,
});
setListComment(response.data);
} catch (error) {
console.log("[ERROR]", error);
}
};
// Update Status
const handlerUpdateStatus = async (value: any) => {
try {
const response = await apiForumUpdateStatus({
id: id as string,
data: value,
});
if (response.success) {
setStatus(response.data);
setData({
...data,
ForumMaster_StatusPosting: {
status: response.data,
},
});
}
} catch (error) {
console.log("[ERROR]", error);
}
};
// Create Commentar
const handlerCreateCommentar = async () => {
const newData = {
comment: text,
authorId: user?.id,
};
try {
setLoadingComment(true);
const response = await apiForumCreateComment({
id: id as string,
data: newData,
});
if (response.success) {
setText("");
const newComment = {
id: response.data.id,
isActive: response.data.isActive,
komentar: response.data.komentar,
createdAt: response.data.createdAt,
authorId: response.data.authorId,
Author: response.data.Author,
};
setListComment((prev) => [newComment, ...(prev || [])]);
setData({
...data,
count: data.count + 1,
});
}
} catch (error) {
console.log("[ERROR]", error);
} finally {
setLoadingComment(false);
}
}; };
return ( return (
<> <>
<ViewWrapper> <ViewWrapper>
{/* <StackCustom> {!data && !listComment ? (
</StackCustom> */} <LoaderCustom />
<Forum_BoxDetailSection ) : (
data={dataDummy} <>
setOpenDrawer={setOpenDrawer} {/* Box Posting */}
setStatus={setStatus} <Forum_BoxDetailSection
/> data={data}
onSetData={() => {
setOpenDrawer(true);
setStatus(data.ForumMaster_StatusPosting?.status);
setAuthorId(data.Author?.id);
setDataId(data.id);
}}
/>
<TextAreaCustom {/* Area Commentar */}
placeholder="Ketik diskusi anda..." {data?.ForumMaster_StatusPosting?.status === "Open" && (
maxLength={1000} <>
showCount <TextAreaCustom
value={text} placeholder="Ketik diskusi anda..."
onChangeText={setText} maxLength={1000}
style={{ showCount
marginBottom: 0, value={text}
}} onChangeText={setText}
/> style={{
<ButtonCustom marginBottom: 0,
style={{ }}
alignSelf: "flex-end", />
}} <ButtonCustom
onPress={() => { isLoading={isLoadingComment}
console.log("Posting", text); style={{
router.back(); alignSelf: "flex-end",
}} }}
> onPress={() => {
Balas handlerCreateCommentar();
</ButtonCustom> }}
>
Balas
</ButtonCustom>
</>
)}
<Spacing height={40} />
<Spacing height={40} /> {/* List Commentar */}
{_.isEmpty(listComment) ? (
{listDummyCommentarForum.map((e, i) => ( <TextCustom align="center" color="gray" size={"small"}>
<Forum_CommentarBoxSection Tidak ada komentar
key={i} </TextCustom>
data={e} ) : (
setOpenDrawer={setOpenDrawerCommentar} <TextCustom color="gray">Komentar :</TextCustom>
/> )}
))} <Spacing height={5} />
{listComment?.map((item: any, index: number) => (
<Forum_CommentarBoxSection
key={index}
data={item}
onSetData={(value) => {
setCommentId(value.setCommentId);
setOpenDrawerCommentar(value.setOpenDrawer);
setCommentAuthorId(value.setCommentAuthorId);
}}
/>
))}
</>
)}
</ViewWrapper> </ViewWrapper>
{/* Posting Drawer */}
<DrawerCustom <DrawerCustom
height={350} height={"auto"}
isVisible={openDrawer} isVisible={openDrawer}
closeDrawer={() => setOpenDrawer(false)} closeDrawer={() => setOpenDrawer(false)}
> >
<Forum_MenuDrawerBerandaSection <Forum_MenuDrawerBerandaSection
id={id as string} id={dataId}
status={status} status={status}
setIsDrawerOpen={() => { setIsDrawerOpen={() => {
setOpenDrawer(false); setOpenDrawer(false);
}} }}
setShowDeleteAlert={setDeleteAlert} authorId={authorId}
setShowAlertStatus={setAlertStatus} handlerUpdateStatus={(value: any) => {
handlerUpdateStatus(value);
}}
/> />
</DrawerCustom> </DrawerCustom>
{/* Alert Status */} {/* Commentar Drawer */}
<AlertCustom
isVisible={alertStatus}
title="Ubah Status Forum"
message="Apakah Anda yakin ingin mengubah status forum ini?"
onLeftPress={() => {
setOpenDrawer(false);
setAlertStatus(false);
console.log("Batal");
}}
onRightPress={() => {
setOpenDrawer(false);
setAlertStatus(false);
console.log("Ubah status forum");
}}
textLeft="Batal"
textRight="Ubah"
colorRight={MainColor.green}
/>
{/* Alert Delete */}
<AlertCustom
isVisible={deleteAlert}
title="Hapus Forum"
message="Apakah Anda yakin ingin menghapus forum ini?"
onLeftPress={() => {
setOpenDrawer(false);
setDeleteAlert(false);
console.log("Batal");
}}
onRightPress={() => {
setOpenDrawer(false);
setDeleteAlert(false);
console.log("Hapus forum");
}}
textLeft="Batal"
textRight="Hapus"
colorRight={MainColor.red}
/>
{/* Commentar */}
<DrawerCustom <DrawerCustom
height={350} height={"auto"}
isVisible={openDrawerCommentar} isVisible={openDrawerCommentar}
closeDrawer={() => setOpenDrawerCommentar(false)} closeDrawer={() => setOpenDrawerCommentar(false)}
> >
<Forum_MenuDrawerCommentar <Forum_MenuDrawerCommentar
id={id as string} id={commentId as string}
commentId={commentId}
commentAuthorId={commentAuthorId}
setIsDrawerOpen={() => { setIsDrawerOpen={() => {
setOpenDrawerCommentar(false); setOpenDrawerCommentar(false);
}} }}
setShowDeleteAlert={setAlertDeleteCommentar} listComment={listComment}
setListComment={setListComment}
countComment={data?.count}
setCountComment={(val: any) => {
setData((prev: any) => ({
...prev,
count: val,
}));
}}
/> />
</DrawerCustom> </DrawerCustom>
{/* Alert Delete Commentar */}
<AlertCustom
isVisible={alertDeleteCommentar}
title="Hapus Komentar"
message="Apakah Anda yakin ingin menghapus komentar ini?"
onLeftPress={() => {
setOpenDrawerCommentar(false);
setAlertDeleteCommentar(false);
console.log("Batal");
}}
onRightPress={() => {
setOpenDrawerCommentar(false);
setAlertDeleteCommentar(false);
console.log("Hapus commentar");
}}
textLeft="Batal"
textRight="Hapus"
colorRight={MainColor.red}
/>
</> </>
); );
} }

View File

@@ -5,27 +5,69 @@ import {
ViewWrapper, ViewWrapper,
} from "@/components"; } from "@/components";
import { MainColor } from "@/constants/color-palet"; import { MainColor } from "@/constants/color-palet";
import { router } from "expo-router"; import { useAuth } from "@/hooks/use-auth";
import { apiForumCreateReportCommentar } from "@/service/api-client/api-master";
import { router, useLocalSearchParams } from "expo-router";
import { useState } from "react";
import Toast from "react-native-toast-message";
export default function ForumOtherReportCommentar() { export default function ForumOtherReportCommentar() {
const { id } = useLocalSearchParams();
const { user } = useAuth();
const [value, setValue] = useState<string>("");
const handlerSubmitReport = async () => {
const newData = {
authorId: user?.id,
description: value,
};
try {
const response = await apiForumCreateReportCommentar({
id: id as string,
data: newData,
});
if (response.success) {
Toast.show({
type: "success",
text1: "Laporan berhasil dikirim",
});
router.back();
}
} catch (error) {
console.log("[ERROR]", error);
Toast.show({
type: "error",
text1: "Gagal",
text2: "Laporan gagal dikirim",
});
}
};
const handleSubmit = ( const handleSubmit = (
<BoxButtonOnFooter> <BoxButtonOnFooter>
<ButtonCustom <ButtonCustom
disabled={!value}
backgroundColor={MainColor.red} backgroundColor={MainColor.red}
textColor={MainColor.white} textColor={MainColor.white}
onPress={() => { onPress={() => {
console.log("Report lainnya"); handlerSubmitReport();
router.back();
}} }}
> >
Report Report
</ButtonCustom> </ButtonCustom>
</BoxButtonOnFooter> </BoxButtonOnFooter>
); );
return ( return (
<> <>
<ViewWrapper footerComponent={handleSubmit}> <ViewWrapper footerComponent={handleSubmit}>
<TextAreaCustom placeholder="Laporkan Komentar" /> <TextAreaCustom
placeholder="Laporkan Komentar"
value={value}
onChangeText={setValue}
/>
</ViewWrapper> </ViewWrapper>
</> </>
); );

View File

@@ -5,17 +5,54 @@ import {
ViewWrapper, ViewWrapper,
} from "@/components"; } from "@/components";
import { MainColor } from "@/constants/color-palet"; import { MainColor } from "@/constants/color-palet";
import { router } from "expo-router"; import { useAuth } from "@/hooks/use-auth";
import { apiForumCreateReportPosting } from "@/service/api-client/api-master";
import { router, useLocalSearchParams } from "expo-router";
import { useState } from "react";
import Toast from "react-native-toast-message";
export default function ForumOtherReportPosting() { export default function ForumOtherReportPosting() {
const { id } = useLocalSearchParams();
const { user } = useAuth();
const [value, setValue] = useState<string>("");
const handlerSubmitReport = async () => {
const newData = {
authorId: user?.id,
description: value,
};
try {
const response = await apiForumCreateReportPosting({
id: id as string,
data: newData,
});
if (response.success) {
Toast.show({
type: "success",
text1: "Laporan berhasil dikirim",
});
router.back();
}
} catch (error) {
console.log("[ERROR]", error);
Toast.show({
type: "error",
text1: "Gagal",
text2: "Laporan gagal dikirim",
});
}
};
const handleSubmit = ( const handleSubmit = (
<BoxButtonOnFooter> <BoxButtonOnFooter>
<ButtonCustom <ButtonCustom
disabled={!value}
backgroundColor={MainColor.red} backgroundColor={MainColor.red}
textColor={MainColor.white} textColor={MainColor.white}
onPress={() => { onPress={() => {
console.log("Report lainnya"); handlerSubmitReport();
router.back();
}} }}
> >
Report Report
@@ -25,7 +62,11 @@ export default function ForumOtherReportPosting() {
return ( return (
<> <>
<ViewWrapper footerComponent={handleSubmit}> <ViewWrapper footerComponent={handleSubmit}>
<TextAreaCustom placeholder="Laporkan Diskusi" /> <TextAreaCustom
placeholder="Laporkan Diskusi"
value={value}
onChangeText={setValue}
/>
</ViewWrapper> </ViewWrapper>
</> </>
); );

View File

@@ -1,41 +1,105 @@
import { import {
ButtonCustom, ButtonCustom,
Spacing, LoaderCustom,
StackCustom, Spacing,
ViewWrapper StackCustom,
ViewWrapper,
} from "@/components"; } from "@/components";
import { AccentColor, MainColor } from "@/constants/color-palet"; import { AccentColor, MainColor } from "@/constants/color-palet";
import { useAuth } from "@/hooks/use-auth";
import Forum_ReportListSection from "@/screens/Forum/ReportListSection"; import Forum_ReportListSection from "@/screens/Forum/ReportListSection";
import { router } from "expo-router"; import { apiForumCreateReportCommentar, apiMasterForumReportList } from "@/service/api-client/api-master";
import { router, useLocalSearchParams } from "expo-router";
import { useState, useEffect } from "react";
import Toast from "react-native-toast-message";
export default function ForumReportCommentar() { export default function ForumReportCommentar() {
const { id } = useLocalSearchParams();
const { user } = useAuth();
const [selectReport, setSelectReport] = useState<string>("");
const [listMaster, setListMaster] = useState<any[] | null>(null);
const [isLoadingList, setIsLoadingList] = useState(false);
useEffect(() => {
onLoadListMaster();
}, []);
const onLoadListMaster = async () => {
try {
setIsLoadingList(true);
const response = await apiMasterForumReportList();
setListMaster(response.data);
} catch (error) {
console.log("[ERROR]", error);
} finally {
setIsLoadingList(false);
}
};
const handlerReport = async () => {
const newData = {
authorId: user?.id,
categoryId: selectReport,
};
try {
const response = await apiForumCreateReportCommentar({
id: id as string,
data: newData,
});
if (response.success) {
Toast.show({
type: "success",
text1: "Laporan berhasil dikirim",
});
router.back();
}
} catch (error) {
console.log("[ERROR]", error);
Toast.show({
type: "error",
text1: "Gagal",
text2: "Laporan gagal dikirim",
});
}
};
return ( return (
<> <>
<ViewWrapper> <ViewWrapper>
<StackCustom> {isLoadingList ? (
<Forum_ReportListSection /> <LoaderCustom />
<ButtonCustom ) : (
backgroundColor={MainColor.red} <StackCustom>
textColor={MainColor.white} <Forum_ReportListSection
onPress={() => { listMaster={listMaster}
console.log("Report"); selectReport={selectReport}
router.back(); setSelectReport={setSelectReport}
}} />
> <ButtonCustom
Report disabled={!selectReport}
</ButtonCustom> backgroundColor={MainColor.red}
<ButtonCustom textColor={MainColor.white}
backgroundColor={AccentColor.blue} onPress={() => {
textColor={MainColor.white} handlerReport();
onPress={() => { }}
console.log("Lainnya"); >
router.replace("/forum/[id]/other-report-commentar"); Report
}} </ButtonCustom>
> <ButtonCustom
Lainnya backgroundColor={AccentColor.blue}
</ButtonCustom> textColor={MainColor.white}
<Spacing/> onPress={() => {
</StackCustom> router.replace(`/forum/${id}/other-report-commentar`);
}}
>
Lainnya
</ButtonCustom>
<Spacing />
</StackCustom>
)}
</ViewWrapper> </ViewWrapper>
</> </>
); );

View File

@@ -1,20 +1,103 @@
import { ViewWrapper, StackCustom, ButtonCustom, Spacing } from "@/components"; import {
import { MainColor, AccentColor } from "@/constants/color-palet"; AlertDefaultSystem,
ButtonCustom,
LoaderCustom,
Spacing,
StackCustom,
ViewWrapper,
} from "@/components";
import { AccentColor, MainColor } from "@/constants/color-palet";
import { useAuth } from "@/hooks/use-auth";
import Forum_ReportListSection from "@/screens/Forum/ReportListSection"; import Forum_ReportListSection from "@/screens/Forum/ReportListSection";
import { router } from "expo-router"; import {
apiForumCreateReportPosting,
apiMasterForumReportList,
} from "@/service/api-client/api-master";
import { router, useLocalSearchParams } from "expo-router";
import { useEffect, useState } from "react";
import Toast from "react-native-toast-message";
export default function ForumReportPosting() { export default function ForumReportPosting() {
return ( const { id } = useLocalSearchParams();
<> const { user } = useAuth();
<ViewWrapper> const [selectReport, setSelectReport] = useState<string>("");
const [listMaster, setListMaster] = useState<any[] | null>(null);
const [isLoadingList, setIsLoadingList] = useState(false);
useEffect(() => {
onLoadListMaster();
}, []);
const onLoadListMaster = async () => {
try {
setIsLoadingList(true);
const response = await apiMasterForumReportList();
setListMaster(response.data);
} catch (error) {
console.log("[ERROR]", error);
} finally {
setIsLoadingList(false);
}
};
const handlerReport = async () => {
const newData = {
authorId: user?.id,
categoryId: selectReport,
};
try {
const response = await apiForumCreateReportPosting({
id: id as string,
data: newData,
});
if (response.success) {
Toast.show({
type: "success",
text1: "Laporan berhasil dikirim",
});
router.back();
}
} catch (error) {
console.log("[ERROR]", error);
Toast.show({
type: "error",
text1: "Gagal",
text2: "Laporan gagal dikirim",
});
}
};
return (
<>
<ViewWrapper>
{isLoadingList ? (
<LoaderCustom />
) : (
<StackCustom> <StackCustom>
<Forum_ReportListSection /> <Forum_ReportListSection
listMaster={listMaster}
selectReport={selectReport}
setSelectReport={setSelectReport}
/>
<ButtonCustom <ButtonCustom
disabled={!selectReport}
backgroundColor={MainColor.red} backgroundColor={MainColor.red}
textColor={MainColor.white} textColor={MainColor.white}
onPress={() => { onPress={() => {
console.log("Report"); AlertDefaultSystem({
router.back(); title: "Laporan Posting",
message: "Apakah anda yakin ingin melaporkan postingan ini?",
textLeft: "Batal",
textRight: "Laporkan",
onPressRight: () => {
handlerReport();
},
});
}} }}
> >
Report Report
@@ -23,15 +106,15 @@ export default function ForumReportPosting() {
backgroundColor={AccentColor.blue} backgroundColor={AccentColor.blue}
textColor={MainColor.white} textColor={MainColor.white}
onPress={() => { onPress={() => {
console.log("Lainnya"); router.replace(`/forum/${id}/other-report-posting`);
router.replace("/forum/[id]/other-report-posting");
}} }}
> >
Lainnya Lainnya
</ButtonCustom> </ButtonCustom>
<Spacing /> <Spacing />
</StackCustom> </StackCustom>
</ViewWrapper> )}
</> </ViewWrapper>
); </>
} );
}

View File

@@ -4,18 +4,47 @@ import {
TextAreaCustom, TextAreaCustom,
ViewWrapper, ViewWrapper,
} from "@/components"; } from "@/components";
import { useAuth } from "@/hooks/use-auth";
import { apiForumCreate } from "@/service/api-client/api-forum";
import { router } from "expo-router"; import { router } from "expo-router";
import { useState } from "react"; import { useState } from "react";
import Toast from "react-native-toast-message";
export default function ForumCreate() { export default function ForumCreate() {
const { user } = useAuth();
const [text, setText] = useState(""); const [text, setText] = useState("");
const [isLoading, setIsLoading] = useState(false);
const handlerSubmit = async () => {
const newData = {
diskusi: text,
authorId: user?.id,
};
try {
setIsLoading(true);
const response = await apiForumCreate({ data: newData });
if (response.success) {
Toast.show({
type: "success",
text1: "Posting berhasil",
});
setText("");
router.back();
}
} catch (error) {
console.log("[ERROR]", error);
} finally {
setIsLoading(false);
}
};
const buttonFooter = ( const buttonFooter = (
<BoxButtonOnFooter> <BoxButtonOnFooter>
<ButtonCustom <ButtonCustom
isLoading={isLoading}
onPress={() => { onPress={() => {
console.log("Posting", text); handlerSubmit();
router.back();
}} }}
> >
Posting Posting

View File

@@ -1,25 +1,58 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { import {
AlertCustom, AvatarComp,
AvatarCustom,
BackButton, BackButton,
DrawerCustom, DrawerCustom,
LoaderCustom,
SearchInput, SearchInput,
TextCustom,
ViewWrapper, ViewWrapper,
} from "@/components"; } from "@/components";
import FloatingButton from "@/components/Button/FloatingButton"; import FloatingButton from "@/components/Button/FloatingButton";
import { MainColor } from "@/constants/color-palet"; 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 { router, Stack } from "expo-router"; import { apiForumGetAll } from "@/service/api-client/api-forum";
import { useState } from "react"; import { apiUser } from "@/service/api-client/api-user";
import { router, Stack, useFocusEffect } from "expo-router";
import _ from "lodash";
import { useCallback, useState } from "react";
export default function Forum() { export default function Forum() {
const id = "test-id-forum";
const [openDrawer, setOpenDrawer] = useState(false); const [openDrawer, setOpenDrawer] = useState(false);
const [status, setStatus] = useState(""); const [status, setStatus] = useState("");
const [alertStatus, setAlertStatus] = useState(false); const { user } = useAuth();
const [deleteAlert, setDeleteAlert] = useState(false); const [dataUser, setDataUser] = useState<any>();
const [listData, setListData] = useState<any[]>();
const [loadingGetList, setLoadingGetList] = useState(false);
const [search, setSearch] = useState("");
const [dataId, setDataId] = useState("");
const [authorId, setAuthorId] = useState("");
useFocusEffect(
useCallback(() => {
onLoadData();
onLoadDataProfile(user?.id as string);
}, [user?.id, search])
);
const onLoadDataProfile = async (id: string) => {
const response = await apiUser(id);
setDataUser(response.data);
};
const onLoadData = async () => {
try {
setLoadingGetList(true);
const response = await apiForumGetAll({ search: search });
setListData(response.data);
} catch (error) {
console.log("[ERROR]", error);
} finally {
setLoadingGetList(false);
}
};
return ( return (
<> <>
@@ -27,12 +60,23 @@ export default function Forum() {
options={{ options={{
title: "Forum", title: "Forum",
headerLeft: () => <BackButton />, headerLeft: () => <BackButton />,
headerRight: () => <AvatarCustom href={`/forum/${id}/forumku`} />, headerRight: () => (
<AvatarComp
fileId={dataUser?.Profile?.imageId}
size="base"
href={`/forum/${user?.id}/forumku`}
/>
),
}} }}
/> />
<ViewWrapper <ViewWrapper
headerComponent={<SearchInput placeholder="Cari topik diskusi" />} headerComponent={
<SearchInput
placeholder="Cari topik diskusi"
onChangeText={(e) => setSearch(e)}
/>
}
floatingButton={ floatingButton={
<FloatingButton <FloatingButton
onPress={() => onPress={() =>
@@ -41,73 +85,45 @@ export default function Forum() {
/> />
} }
> >
{listDummyDiscussionForum.map((e, i) => ( {loadingGetList ? (
<Forum_BoxDetailSection <LoaderCustom />
key={i} ) : _.isEmpty(listData) ? (
data={e} <TextCustom align="center" color="gray">
setOpenDrawer={setOpenDrawer} Tidak ada diskusi
setStatus={setStatus} </TextCustom>
isTruncate={true} ) : (
href={`/forum/${id}`} listData?.map((e: any, i: number) => (
/> <Forum_BoxDetailSection
))} key={i}
data={e}
onSetData={() => {
setDataId(e.id);
setOpenDrawer(true);
setStatus(e.ForumMaster_StatusPosting?.status);
setAuthorId(e.Author?.id);
}}
isTruncate={true}
href={`/forum/${e.id}`}
isRightComponent={false}
/>
))
)}
</ViewWrapper> </ViewWrapper>
<DrawerCustom <DrawerCustom
height={350} height={"auto"}
isVisible={openDrawer} isVisible={openDrawer}
closeDrawer={() => setOpenDrawer(false)} closeDrawer={() => setOpenDrawer(false)}
> >
<Forum_MenuDrawerBerandaSection <Forum_MenuDrawerBerandaSection
id={id} id={dataId}
authorId={authorId}
status={status} status={status}
setIsDrawerOpen={() => { setIsDrawerOpen={() => {
setOpenDrawer(false); setOpenDrawer(false);
}} }}
setShowDeleteAlert={setDeleteAlert}
setShowAlertStatus={setAlertStatus}
/> />
</DrawerCustom> </DrawerCustom>
{/* Alert Status */}
<AlertCustom
isVisible={alertStatus}
title="Ubah Status Forum"
message="Apakah Anda yakin ingin mengubah status forum ini?"
onLeftPress={() => {
setOpenDrawer(false);
setAlertStatus(false);
console.log("Batal");
}}
onRightPress={() => {
setOpenDrawer(false);
setAlertStatus(false);
console.log("Ubah status forum");
}}
textLeft="Batal"
textRight="Ubah"
colorRight={MainColor.green}
/>
{/* Alert Delete */}
<AlertCustom
isVisible={deleteAlert}
title="Hapus Forum"
message="Apakah Anda yakin ingin menghapus forum ini?"
onLeftPress={() => {
setOpenDrawer(false);
setDeleteAlert(false);
console.log("Batal");
}}
onRightPress={() => {
setOpenDrawer(false);
setDeleteAlert(false);
console.log("Hapus forum");
}}
textLeft="Batal"
textRight="Hapus"
colorRight={MainColor.red}
/>
</> </>
); );
} }

View File

@@ -1,19 +1,38 @@
import { import {
BaseBox,
FloatingButton, FloatingButton,
Grid, LoaderCustom,
ProgressCustom, ViewWrapper
StackCustom,
TextCustom,
ViewWrapper,
} from "@/components"; } from "@/components";
import DUMMY_IMAGE from "@/constants/dummy-image-value"; import NoDataText from "@/components/_ShareComponent/NoDataText";
import dayjs from "dayjs"; import Investment_BoxBerandaSection from "@/screens/Invesment/BoxBerandaSection";
import { Image } from "expo-image"; import { apiInvestmentGetAll } from "@/service/api-client/api-investment";
import { router } from "expo-router"; import { router, useFocusEffect } from "expo-router";
import { View } from "react-native"; import _ from "lodash";
import { useCallback, useState } from "react";
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,61 +40,15 @@ 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}> <NoDataText />
<Image ) : (
source={DUMMY_IMAGE.background} list?.map((item: any, index: number) => (
style={{ width: "auto", height: 100, borderRadius: 10 }} <Investment_BoxBerandaSection id={item.id} data={item} key={index} />
/> ))
</Grid.Col> )}
<Grid.Col span={1}>
<View />
</Grid.Col>
<Grid.Col span={6}>
<StackCustom>
<TextCustom truncate={2}>
Title here : Lorem ipsum dolor sit amet consectetur
adipisicing elit. Omnis, exercitationem, sequi enim quod
distinctio maiores laudantium amet, quidem atque repellat sit
vitae qui aliquam est veritatis laborum eum voluptatum totam!
</TextCustom>
<ProgressCustom value={index % 5 * 20} size="lg" />
<TextCustom>
Sisa waktu: {dayjs().diff(dayjs(), "day")} hari
</TextCustom>
</StackCustom>
</Grid.Col>
</Grid>
</BaseBox>
))}
</ViewWrapper> </ViewWrapper>
); );
} }
// <View style={{ padding: 20, gap: 16 }}>
// <TextCustom>Progress 70%</TextCustom>
// <ProgressCustom value={70} color="primary" size="md" />
// <TextCustom>Success Progress</TextCustom>
// <ProgressCustom value={40} color="success" size="lg" />
// <TextCustom>Warning Progress (small)</TextCustom>
// <ProgressCustom value={90} color="warning" size="sm" />
// <TextCustom>Error Indeterminate</TextCustom>
// <ProgressCustom value={null} color="error" size="md" />
// <TextCustom>Custom Radius</TextCustom>
// <ProgressCustom value={60} color="info" size="xl" radius={4} />
// <ProgressCustom value={70} color="primary" size="lg" />
// <ProgressCustom value={45} color="success" size="md" label="Halfway!" />
// <ProgressCustom value={90} color="warning" size="lg" showLabel={false} />
// <ProgressCustom value={null} color="error" size="sm" label="Loading..." />
// </View>;

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

@@ -4,6 +4,7 @@ import { MainColor } from "@/constants/color-palet";
import { ICON_SIZE_SMALL } from "@/constants/constans-value"; import { ICON_SIZE_SMALL } from "@/constants/constans-value";
import Job_BoxDetailSection from "@/screens/Job/BoxDetailSection"; import Job_BoxDetailSection from "@/screens/Job/BoxDetailSection";
import { apiJobGetOne } from "@/service/api-client/api-job"; import { apiJobGetOne } from "@/service/api-client/api-job";
import { BASE_URL } from "@/service/api-config";
import { Ionicons } from "@expo/vector-icons"; import { Ionicons } from "@expo/vector-icons";
import * as Clipboard from "expo-clipboard"; import * as Clipboard from "expo-clipboard";
import { useLocalSearchParams } from "expo-router"; import { useLocalSearchParams } from "expo-router";
@@ -32,7 +33,8 @@ export default function JobDetail() {
} }
}; };
const linkUrl = `http://192.168.1.83:3000/job-vacancy/`; const baseUrl = BASE_URL;
const linkUrl = `${baseUrl}/job-vacancy/`;
const OpenLinkButton = ({ id }: { id: string }) => { const OpenLinkButton = ({ id }: { id: string }) => {
const jobUrl = `${linkUrl}${id}`; const jobUrl = `${linkUrl}${id}`;

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,
}); });

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