Compare commits

...

55 Commits

Author SHA1 Message Date
624bd49f69 QC Admin ( Inno )
Fix:
   modified:   android/app/build.gradle
        modified:   app.config.js
        modified:   app/(application)/admin/donation/[id]/[status]/index.tsx
        modified:   app/(application)/admin/donation/[id]/[status]/transaction-detail.tsx
        modified:   app/(application)/admin/donation/[id]/detail-disbursement-of-funds.tsx
        modified:   app/(application)/admin/donation/category.tsx
        modified:   app/(application)/admin/event/[id]/[status]/index.tsx
        modified:   app/(application)/admin/event/[id]/list-of-participants.tsx
        modified:   app/(application)/admin/event/[status]/status.tsx
        modified:   app/(application)/admin/forum/[id]/index.tsx
        modified:   app/(application)/admin/forum/[id]/list-report-comment.tsx
        modified:   app/(application)/admin/forum/[id]/list-report-posting.tsx
        modified:   app/(application)/admin/investment/[id]/[status]/index.tsx
        modified:   app/(application)/admin/investment/[id]/[status]/transaction-detail.tsx
        modified:   app/(application)/admin/voting/[id]/[status]/index.tsx
        modified:   components/DateInput/DateTimeIOS.tsx
        modified:   components/_ShareComponent/Admin/ButtonReject.tsx
        deleted:    components/_ShareComponent/GridDetail_4_8.tsx

Add:/
components/_ShareComponent/GridSpan_4_8.tsx

### No Issue
2025-12-05 17:20:39 +08:00
2446e9d51a Fix apple reject EULA
Add:
components/Alert/AlertWarning.ts
        utils/badWordsIndonesia.ts

Fix:
- app.config.js
- app/(application)/(user)/forum/[id]/edit.tsx
- app/(application)/(user)/forum/[id]/index.tsx
- app/(application)/(user)/forum/create.tsx
- ios/HIPMIBadungConnect/Info.plist

### No Issue
2025-12-05 11:46:36 +08:00
ab5733f336 Fix redirect admin 2025-12-04 17:41:19 +08:00
f5e30087ed Fix QC Inno
Fix:
- app/(application)/admin/donation/category-create.tsx
- app/(application)/admin/donation/category-update.tsx
- app/(application)/admin/donation/category.tsx
- components/_ShareComponent/Admin/TableValue.tsx
- screens/Authentication/LoginView.tsx
- service/api-admin/api-master-admin.ts

### No Issue
2025-12-04 16:59:39 +08:00
a93f97ed6a Fix rejected Apple
Add:
-  utils/viersionBadge.ts

Fix:
- app.config.js
- context/AuthContext.tsx
- ios/HIPMIBadungConnect/Info.plist
- screens/Authentication/LoginView.tsx
- screens/Authentication/VerificationView.tsx
- service/api-config.ts

### No Issue
2025-12-03 17:23:12 +08:00
858b441a8c Clearing apple rejected
QC: Inno

Fix:
- app.config.js
- app/(application)/(user)/investment/[id]/index.tsx
- app/(application)/(user)/voting/(tabs)/index.tsx
- app/(application)/(user)/waiting-room.tsx
- app/(application)/terms-agreement.tsx
- context/AuthContext.tsx
- ios/HIPMIBadungConnect.xcodeproj/project.pbxproj
- ios/HIPMIBadungConnect/Info.plist
- screens/Authentication/LoginView.tsx
- screens/Authentication/VerificationView.tsx
- screens/Home/topFeatureSection.tsx
- screens/Invesment/BoxBerandaSection.tsx
- screens/Invesment/ButtonInvestasiSection.tsx
- screens/Invesment/DetailDataPublishSection.tsx
- service/api-client/api-voting.ts
- service/api-config.ts

### No Issue
2025-12-02 17:48:24 +08:00
98aaa126a1 QC: Inno dan Pak Jun
Fix:
- app/(application)/(user)/collaboration/create.tsx
- app/(application)/(user)/event/[id]/edit.tsx
- app/(application)/(user)/event/create.tsx
- app/(application)/(user)/profile/[id]/blocked-list.tsx
- app/(application)/(user)/profile/[id]/index.tsx
- app/(application)/(user)/voting/[id]/[status]/detail.tsx
- components/Button/FloatingButton.tsx
- components/TextArea/TextAreaCustom.tsx
- components/TextInput/TextInputCustom.tsx
- constants/color-palet.ts
- screens/Authentication/LoginView.tsx
- screens/Home/topFeatureSection.tsx
- screens/Portofolio/SocialMediaSection.tsx
- screens/Voting/BoxDetailHasilVotingSection.tsx
- styles/global-styles.ts

### No Issue
2025-12-01 17:43:20 +08:00
69452ff4e7 Fix loader fetch data di Forum dan Forumku
### No Issue
2025-11-28 17:27:59 +08:00
33ec892ec8 Prebuild : untuk Maps box
### No issue
2025-11-28 16:35:18 +08:00
8a900e9469 Halaman unblock user
Add:
- app/(application)/(user)/profile/[id]/blocked-list.tsx
app/(application)/(user)/profile/[id]/detail-blocked.tsx
components/_ShareComponent/ListEmptyComponent.tsx
components/_ShareComponent/ListLoaderFooterComponent.tsx
components/_ShareComponent/ListSkeletonComponent.tsx
hooks/use-paginated-api.ts
service/api-client/api-blocked.ts

Fix:
modified:   app/(application)/(user)/profile/_layout.tsx
modified:   components/_ShareComponent/NewWrapper.tsx
modified:   components/index.ts
modified:   screens/Profile/ListPage.tsx
modified:   styles/global-styles.ts

### No Issue
2025-11-28 13:55:48 +08:00
d471682ae7 Refresh control dan Blockir user di forum
### No Issue
2025-11-26 16:13:05 +08:00
00eea71248 Penambahan fitur block user: 50%
Fix:
- app/(application)/(user)/forum/[id]/index.tsx
- app/(application)/(user)/home.tsx
- screens/Forum/ListPage.tsx
- screens/Forum/MenuDrawerSection.tsx/MenuBeranda.tsx
- service/api-client/api-master.ts
- service/api-client/api-user.ts

### No Issue
2025-11-25 11:04:12 +08:00
41e648d8f3 Fix rejected Apple :
Submission ID: 1efcd8eb-7d68-4348-9925-43a8e1bd7d1e

Add:
-  app/(application)/terms-agreement.tsx

Fix:
- app/(application)/(user)/home.tsx
- app/(application)/_layout.tsx
- context/AuthContext.tsx
- ios/HIPMIBadungConnect/Info.plist
- screens/Authentication/LoginView.tsx
- screens/Authentication/RegisterView.tsx
- service/api-config.ts
- types/User.ts

### NO Issue
2025-11-24 17:09:52 +08:00
0c4deac6e2 Fix map android :
Add:
- screens/Maps/

Fix:
- android/app/src/main/AndroidManifest.xml
- app.config.js
- app/(application)/(user)/maps/index.tsx
- bun.lock
- ios/HIPMIBadungConnect.xcodeproj/project.pbxproj
- ios/HIPMIBadungConnect/Info.plist
- ios/Podfile.lock
- package.json

### No Issue
2025-11-21 17:43:58 +08:00
676b8a38be Fix apple rejected:
-  app/(application)/(user)/delete-account.tsx
-  screens/Profile/menuDrawerSection.tsx

### No Issue
2025-11-20 15:42:37 +08:00
0a2aa71013 Add:
-  app/(application)/(user)/delete-account.tsx
-  assets/images/constants/logo-hipmi_back.png

Fix:
- app/(application)/(user)/_layout.tsx
- assets/images/constants/logo-hipmi.png
- components/Grid/GridCustom.tsx
- screens/Profile/ListPage.tsx
- screens/Profile/menuDrawerSection.tsx
- service/api-client/api-user.ts

### No Issue
2025-11-19 17:40:35 +08:00
868e96a54a Try to notification
### Issue: package import * as Notifications from expo-notifications;
2025-11-18 17:46:33 +08:00
059b4d053a Fix rejected apple delete account & start for notification
### No issue
2025-11-18 14:29:02 +08:00
76debfd6a6 Add:
-  google-services.json
- utils/notifications.ts

Fix:
- app.config.js : tambah access googleServicesFile
- app/_layout.tsx : tambah untuk push notifikasi
- package.json: install install expo-notifications expo-device expo-constants

### No Issue
2025-11-14 17:44:22 +08:00
8c3aec8e57 Admin Event: QR Code sudah bisa di scan
### No issue
2025-11-14 14:44:36 +08:00
1ade69ff2f Fix QR Code Access
### No Issue
2025-11-13 17:41:54 +08:00
97ea6ab799 1.0.1 2025-11-12 17:34:57 +08:00
4e9ce07759 Penambahan akses untuk QR COde pada file:
-  app.config.js
- service/api-config.ts

### No Issue
2025-11-12 17:34:35 +08:00
8f659c2b7e tambah repo 2025-11-12 14:06:55 +08:00
61bca7cfe1 tambah repo 2025-11-12 14:06:36 +08:00
5af85c3a8b tambah repo 2025-11-12 13:58:55 +08:00
a8807d88ad Staging & Change logo
### No Issue
2025-11-10 10:59:48 +08:00
5d36429aa4 Merge pull request 'Integrasi API' (#7) from api/4-nov-25 into main
Reviewed-on: http://wibugit.wibudev.com/wibu/hipmi-mobile/pulls/7
2025-11-04 12:14:26 +08:00
ec49999f99 Integrasi API:
Add:
-  hipmi-note.md

Fix:
- app/(application)/(user)/donation/[id]/(transaction-flow)/[invoiceId]/failed.tsx
- app/(application)/(user)/donation/[id]/(transaction-flow)/[invoiceId]/success.tsx
- app/(application)/(user)/event/[id]/confirmation.tsx
- app/(application)/(user)/investment/(tabs)/index.tsx
- app/(application)/(user)/investment/(tabs)/my-holding.tsx
- app/(application)/(user)/investment/[id]/(my-holding)/[id].tsx
- app/(application)/(user)/investment/[id]/(transaction-flow)/failed.tsx
- app/(application)/(user)/investment/[id]/(transaction-flow)/index.tsx
- app/(application)/(user)/investment/[id]/(transaction-flow)/success.tsx
- app/(application)/(user)/investment/[id]/investor.tsx
- app/(application)/admin/investment/[id]/[status]/index.tsx
- app/(application)/admin/investment/[id]/[status]/transaction-detail.tsx
- app/(application)/admin/investment/[id]/list-of-investor.tsx
- lib/dummy-data/investment/dummy-data-not-publish.ts
- screens/Authentication/VerificationView.tsx
- screens/Home/bottomFeatureSection.tsx
- service/api-client/api-investment.ts

### No Issue
2025-11-04 12:13:49 +08:00
867e82c6fa Merge pull request 'Admin : Investasi integarsi API' (#6) from api-admin/31-oct-25 into main
Reviewed-on: http://wibugit.wibudev.com/wibu/hipmi-mobile/pulls/6
2025-11-03 11:45:34 +08:00
f9f996f195 Admin : Investasi integarsi API
### NO Issue
2025-11-03 11:41:31 +08:00
98394309e6 Merge pull request 'Integrasi API: Investment & Admin Investment' (#5) from api-admin/30-oct-25 into main
Reviewed-on: http://wibugit.wibudev.com/wibu/hipmi-mobile/pulls/5
2025-10-30 17:43:51 +08:00
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
f9d9b5fbaa Merge pull request 'Integrasi API: Donation & Admin Donation' (#4) from api-admin/29-oct-25 into main
Reviewed-on: http://wibugit.wibudev.com/wibu/hipmi-mobile/pulls/4
2025-10-29 17:38:03 +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
31c1b35173 Merge pull request 'Integrasi API: Donation & Admin Donation' (#3) from api-admin/28-oct-25 into main
Reviewed-on: http://wibugit.wibudev.com/wibu/hipmi-mobile/pulls/3
2025-10-28 17:51:23 +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
de0280367f Merge pull request 'Integrasi API: Donation Admin' (#2) from api-admin/27-oct-25 into main
Reviewed-on: http://wibugit.wibudev.com/wibu/hipmi-mobile/pulls/2
2025-10-28 10:21:39 +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
c8b14b816f Merge pull request 'New repo mobile after delete !' (#1) from api/24-oct-25 into main
Reviewed-on: http://wibugit.wibudev.com/wibu/hipmi-mobile/pulls/1
2025-10-27 11:32:15 +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
248 changed files with 11283 additions and 3953 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)
@@ -82,6 +82,14 @@ def enableProguardInReleaseBuilds = (findProperty('android.enableProguardInRelea
def jscFlavor = 'io.github.react-native-community:jsc-android:2026004.+' def jscFlavor = 'io.github.react-native-community:jsc-android:2026004.+'
android { android {
// @generated begin @rnmapbox/maps-libcpp - expo prebuild (DO NOT MODIFY) sync-e24830a5a3e854b398227dfe9630aabfaa1cadd1
packagingOptions {
pickFirst 'lib/x86/libc++_shared.so'
pickFirst 'lib/x86_64/libc++_shared.so'
pickFirst 'lib/arm64-v8a/libc++_shared.so'
pickFirst 'lib/armeabi-v7a/libc++_shared.so'
}
// @generated end @rnmapbox/maps-libcpp
ndkVersion rootProject.ext.ndkVersion ndkVersion rootProject.ext.ndkVersion
buildToolsVersion rootProject.ext.buildToolsVersion buildToolsVersion rootProject.ext.buildToolsVersion
@@ -92,8 +100,10 @@ android {
applicationId 'com.bip.hipmimobileapp' applicationId 'com.bip.hipmimobileapp'
minSdkVersion rootProject.ext.minSdkVersion minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 1 versionCode 3
versionName "1.0.0" versionName "1.0.1"
buildConfigField "String", "REACT_NATIVE_RELEASE_LEVEL", "\"${findProperty('reactNativeReleaseLevel') ?: 'stable'}\""
} }
signingConfigs { signingConfigs {
debug { debug {
@@ -111,15 +121,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 {
@@ -175,3 +188,5 @@ dependencies {
implementation jscFlavor implementation jscFlavor
} }
} }
apply plugin: 'com.google.gms.google-services'

View File

@@ -0,0 +1,29 @@
{
"project_info": {
"project_number": "608461535079",
"project_id": "hipmi-badung-connect",
"storage_bucket": "hipmi-badung-connect.firebasestorage.app"
},
"client": [
{
"client_info": {
"mobilesdk_app_id": "1:608461535079:android:4ff12ddc283fb3746761c2",
"android_client_info": {
"package_name": "com.bip.hipmimobileapp"
}
},
"oauth_client": [],
"api_key": [
{
"current_key": "AIzaSyBiDtIk3Q9zffFwIdJ5cjqY7e4390JGSkM"
}
],
"services": {
"appinvite_service": {
"other_platform_oauth_client": []
}
}
}
],
"configuration_version": "1"
}

View File

@@ -1,4 +1,6 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools"> <manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.CAMERA"/> <uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
@@ -13,7 +15,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"/>
@@ -29,6 +31,12 @@
<data android:scheme="hipmimobile"/> <data android:scheme="hipmimobile"/>
<data android:scheme="exp+hipmi-mobile"/> <data android:scheme="exp+hipmi-mobile"/>
</intent-filter> </intent-filter>
<intent-filter android:autoVerify="true" data-generated="true">
<action android:name="android.intent.action.VIEW"/>
<data android:scheme="https" android:host="cld-dkr-staging-hipmi.wibudev.com" android:pathPrefix="/"/>
<category android:name="android.intent.category.BROWSABLE"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity> </activity>
</application> </application>
</manifest> </manifest>

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 97 KiB

After

Width:  |  Height:  |  Size: 154 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 151 KiB

After

Width:  |  Height:  |  Size: 231 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.3 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.0 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.3 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 30 KiB

View File

@@ -1,5 +1,5 @@
<resources> <resources>
<string name="app_name">HIPMI BADUNG</string> <string name="app_name">HIPMI Badung Connect</string>
<string name="expo_system_ui_user_interface_style" translatable="false">automatic</string> <string name="expo_system_ui_user_interface_style" translatable="false">automatic</string>
<string name="expo_splash_screen_resize_mode" translatable="false">contain</string> <string name="expo_splash_screen_resize_mode" translatable="false">contain</string>
<string name="expo_splash_screen_status_bar_translucent" translatable="false">false</string> <string name="expo_splash_screen_status_bar_translucent" translatable="false">false</string>

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

@@ -6,27 +6,15 @@ buildscript {
mavenCentral() mavenCentral()
} }
dependencies { dependencies {
classpath 'com.google.gms:google-services:4.4.1'
classpath('com.android.tools.build:gradle') classpath('com.android.tools.build:gradle')
classpath('com.facebook.react:react-native-gradle-plugin') classpath('com.facebook.react:react-native-gradle-plugin')
classpath('org.jetbrains.kotlin:kotlin-gradle-plugin') classpath('org.jetbrains.kotlin:kotlin-gradle-plugin')
} }
} }
def reactNativeAndroidDir = new File(
providers.exec {
workingDir(rootDir)
commandLine("node", "--print", "require.resolve('react-native/package.json')")
}.standardOutput.asText.get().trim(),
"../android"
)
allprojects { 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' }
@@ -35,3 +23,25 @@ allprojects {
apply plugin: "expo-root-project" apply plugin: "expo-root-project"
apply plugin: "com.facebook.react.rootproject" apply plugin: "com.facebook.react.rootproject"
// @generated begin @rnmapbox/maps-v2-maven - expo prebuild (DO NOT MODIFY) sync-d4ccbfdff48fdba3138b02a8ba41b9722af001d8
allprojects {
repositories {
maven {
url 'https://api.mapbox.com/downloads/v2/releases/maven'
// Authentication is no longer required as per Mapbox's removal of download token requirement
// See: https://github.com/mapbox/mapbox-maps-flutter/issues/775
// Keeping this as optional for backward compatibility
def token = project.properties['MAPBOX_DOWNLOADS_TOKEN'] ?: System.getenv('RNMAPBOX_MAPS_DOWNLOAD_TOKEN')
if (token) {
authentication { basic(BasicAuthentication) }
credentials {
username = 'mapbox'
password = token
}
}
}
}
}
// @generated end @rnmapbox/maps-v2-maven

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
# 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 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

@@ -31,7 +31,7 @@ extensions.configure(com.facebook.react.ReactSettingsExtension) { ex ->
} }
expoAutolinking.useExpoModules() expoAutolinking.useExpoModules()
rootProject.name = 'HIPMI BADUNG' rootProject.name = 'HIPMI Badung Connect'
expoAutolinking.useExpoVersionCatalog() expoAutolinking.useExpoVersionCatalog()

View File

@@ -1,61 +1,82 @@
// app.config.js // app.config.js
require('dotenv').config(); require("dotenv").config();
export default { export default {
name: 'HIPMI BADUNG', name: "HIPMI Badung Connect",
slug: 'hipmi-mobile', slug: "hipmi-mobile",
version: '1.0.0', version: "1.0.1",
orientation: 'portrait', orientation: "portrait",
icon: './assets/images/icon.png', icon: "./assets/images/icon.png",
scheme: 'hipmimobile', scheme: "hipmimobile",
userInterfaceStyle: 'automatic', userInterfaceStyle: "automatic",
newArchEnabled: true, newArchEnabled: true,
ios: { ios: {
supportsTablet: true, supportsTablet: true,
bundleIdentifier: 'com.anonymous.hipmi-mobile', bundleIdentifier: "com.anonymous.hipmi-mobile",
infoPlist: { infoPlist: {
ITSAppUsesNonExemptEncryption: false, ITSAppUsesNonExemptEncryption: false,
"NSLocationWhenInUseUsageDescription": "Aplikasi membutuhkan akses lokasi untuk menampilkan peta.",
}, },
associatedDomains: ["applinks:cld-dkr-staging-hipmi.wibudev.com"],
buildNumber: "13",
}, },
android: { android: {
googleServicesFile: "./google-services.json",
adaptiveIcon: { adaptiveIcon: {
foregroundImage: './assets/images/splash-icon.png', foregroundImage: "./assets/images/splash-icon.png",
backgroundColor: '#ffffff', backgroundColor: "#ffffff",
}, },
edgeToEdgeEnabled: true, edgeToEdgeEnabled: true,
package: 'com.bip.hipmimobileapp', package: "com.bip.hipmimobileapp",
versionCode: 3,
// softwareKeyboardLayoutMode: 'resize', // option: untuk mengatur keyboard pada room chst collaboration // softwareKeyboardLayoutMode: 'resize', // option: untuk mengatur keyboard pada room chst collaboration
intentFilters: [
{
action: "VIEW",
autoVerify: true, // wajib untuk App Links
data: [
{
scheme: "https",
host: "cld-dkr-staging-hipmi.wibudev.com",
pathPrefix: "/",
},
],
category: ["BROWSABLE", "DEFAULT"],
},
],
}, },
web: { web: {
bundler: 'metro', bundler: "metro",
output: 'static', output: "static",
favicon: './assets/images/favicon.png', favicon: "./assets/images/favicon.png",
}, },
plugins: [ plugins: [
'expo-router', "expo-router",
'expo-web-browser', "expo-notifications",
"expo-web-browser",
[ [
'expo-splash-screen', "expo-splash-screen",
{ {
image: './assets/images/splash-icon.png', image: "./assets/images/splash-icon.png",
imageWidth: 200, imageWidth: 200,
resizeMode: 'contain', resizeMode: "contain",
backgroundColor: '#ffffff', backgroundColor: "#ffffff",
}, },
], ],
[ [
'expo-camera', "expo-camera",
{ {
cameraPermission: 'Allow $(PRODUCT_NAME) to access your camera', cameraPermission: "Allow $(PRODUCT_NAME) to access your camera",
microphonePermission: 'Allow $(PRODUCT_NAME) to access your microphone', microphonePermission: "Allow $(PRODUCT_NAME) to access your microphone",
recordAudioAndroid: true, recordAudioAndroid: true,
}, },
], ],
'expo-font', "expo-font",
"@rnmapbox/maps",
], ],
experiments: { experiments: {
@@ -65,10 +86,11 @@ export default {
extra: { extra: {
router: {}, router: {},
eas: { eas: {
projectId: '5cf15964-4889-4755-b8ed-b99c61d614d1', projectId: "5cf15964-4889-4755-b8ed-b99c61d614d1",
}, },
// Tambahkan environment variables ke sini // Tambahkan environment variables ke sini
API_BASE_URL: process.env.API_BASE_URL, API_BASE_URL: process.env.API_BASE_URL,
BASE_URL: process.env.BASE_URL, BASE_URL: process.env.BASE_URL,
DEEP_LINK_URL: process.env.DEEP_LINK_URL,
}, },
}; };

View File

@@ -10,6 +10,13 @@ export default function UserLayout() {
return ( return (
<> <>
<Stack screenOptions={HeaderStyles}> <Stack screenOptions={HeaderStyles}>
<Stack.Screen
name="delete-account"
options={{
title: "Hapus Akun",
headerLeft: () => <BackButton />,
}}
/>
<Stack.Screen <Stack.Screen
name="waiting-room" name="waiting-room"
options={{ options={{

View File

@@ -155,7 +155,7 @@ export default function CollaborationCreate() {
<TextAreaCustom <TextAreaCustom
required required
label="Keuntungan Proyek" label="Keuntungan Proyek"
placeholder="Masukan keuntungan proyek" placeholder="Masukan keuntungan proyek, contoh: Meningkatkan relasi bisnis , menjamin kualitas produk, meningkatkan kinerja dan lain lain"
showCount showCount
maxLength={1000} maxLength={1000}
value={data?.benefit} value={data?.benefit}

View File

@@ -0,0 +1,111 @@
import {
AlertDefaultSystem,
BaseBox,
ButtonCustom,
CenterCustom,
StackCustom,
TextCustom,
TextInputCustom,
ViewWrapper,
} from "@/components";
import { useAuth } from "@/hooks/use-auth";
import { apiDeleteUser } from "@/service/api-client/api-user";
import { Image } from "expo-image";
import { useLocalSearchParams } from "expo-router/build/hooks";
import { useState } from "react";
import Toast from "react-native-toast-message";
export default function DeleteAccount() {
const { token, logout, user } = useAuth();
const { phone } = useLocalSearchParams();
const [text, setText] = useState("");
const [isLoading, setLoading] = useState(false);
const deleteAccount = async () => {
if (text !== "Delete Account") {
return Toast.show({
type: "error",
text1: "Ketik 'Delete Account' untuk menghapus akun",
});
}
AlertDefaultSystem({
title: "Anda yakin akan menghapus akun ini?",
message:
"Semua data yang pernah anda buat akan terhapus secara permanen !",
textLeft: "Batal",
textRight: "Ya",
onPressRight: async () => {
try {
setLoading(true);
const response = await apiDeleteUser({ id: user?.id as string });
if (response.success) {
console.log("RESPONSE >> ", response);
Toast.show({
type: "success",
text1: "Akun berhasil dihapus",
});
setTimeout(() => {
logout();
setLoading(false);
}, 2000);
} else {
Toast.show({
type: "error",
text1: "Gagal menghapus akun",
});
setLoading(false);
}
} catch (error) {
console.log("ERROR >> ", error);
setLoading(false);
}
},
});
};
return (
<>
<ViewWrapper>
<StackCustom>
<BaseBox>
<StackCustom>
<CenterCustom>
<Image
source={require("@/assets/images/constants/logo-hipmi.png")}
style={{
width: 150,
height: 150,
}}
/>
</CenterCustom>
<TextCustom align="center">
Anda akan menghapus akun dengan nomor +{phone}
</TextCustom>
<TextCustom align="center">
Ketik 'Delete Account' untuk menghapus akun
</TextCustom>
<TextInputCustom
value={text}
onChangeText={setText}
placeholder="Ketik 'Delete Account'"
/>
<ButtonCustom
backgroundColor="red"
textColor="white"
onPress={deleteAccount}
isLoading={isLoading}
disabled={isLoading}
>
Submit
</ButtonCustom>
</StackCustom>
</BaseBox>
</StackCustom>
</ViewWrapper>
</>
);
}

View File

@@ -1,3 +1,4 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { import {
BaseBox, BaseBox,
Grid, Grid,
@@ -7,11 +8,60 @@ import {
ViewWrapper, ViewWrapper,
} from "@/components"; } from "@/components";
import { MainColor } from "@/constants/color-palet"; import { MainColor } from "@/constants/color-palet";
import { apiDonationGetInvoiceById } from "@/service/api-client/api-donation";
import { GStyles } from "@/styles/global-styles"; import { GStyles } from "@/styles/global-styles";
import { dateTimeView } from "@/utils/dateTimeView";
import { formatCurrencyDisplay } from "@/utils/formatCurrencyDisplay";
import { FontAwesome6 } from "@expo/vector-icons"; import { FontAwesome6 } from "@expo/vector-icons";
import dayjs from "dayjs"; import { useFocusEffect, useLocalSearchParams } from "expo-router";
import { useCallback, useState } from "react";
export default function DonasiFailed() { export default function DonasiFailed() {
const { id, invoiceId } = useLocalSearchParams();
const [data, setData] = useState<any>(null);
useFocusEffect(
useCallback(() => {
onLoadData();
}, [id, invoiceId])
);
const onLoadData = async () => {
try {
const response = await apiDonationGetInvoiceById({
id: invoiceId as string,
});
console.log("[DATA]", JSON.stringify(response.data, null, 2));
setData(response.data);
} catch (error) {
console.log("[ERROR]", error);
}
};
const listData = [
{
label: "Bank",
value: (data && data?.MasterBank?.namaBank) || "-",
},
{
label: "Rekening Penerima",
value: (data && data?.MasterBank?.namaAkun) || "-",
},
{
label: "No Rekening",
value: (data && data?.MasterBank?.norek) || "-",
},
{
label: "Jumlah Donasi",
value: (data && formatCurrencyDisplay(data?.nominal)) || "-",
},
{
label: "Tanggal",
value: (data && dateTimeView({ date: data?.createdAt })) || "-",
},
];
return ( return (
<ViewWrapper> <ViewWrapper>
<StackCustom> <StackCustom>
@@ -58,26 +108,3 @@ export default function DonasiFailed() {
</ViewWrapper> </ViewWrapper>
); );
} }
const listData = [
{
label: "Bank",
value: " BCA",
},
{
label: "Rekening Penerima",
value: "Himpunan Pengusaha Muda Indonesia",
},
{
label: "No Rekening",
value: "2304235678854332",
},
{
label: "Jumlah Donasi",
value: "Rp. 750.000",
},
{
label: "Tanggal",
value: `${dayjs(new Date()).format("DD/MM/YYYY")}`,
},
];

View File

@@ -122,7 +122,7 @@ export default function DonationInvoice() {
}} }}
> >
<TextCustom size="xlarge" bold color="yellow"> <TextCustom size="xlarge" bold color="yellow">
{data?.DonasiMaster_Bank?.norek} {data?.MasterBank?.norek}
</TextCustom> </TextCustom>
</Grid.Col> </Grid.Col>
<Grid.Col <Grid.Col
@@ -131,7 +131,7 @@ export default function DonationInvoice() {
alignItems: "flex-end", alignItems: "flex-end",
}} }}
> >
<CopyButton textToCopy={data?.DonasiMaster_Bank?.norek} /> <CopyButton textToCopy={data?.MasterBank?.norek} />
</Grid.Col> </Grid.Col>
</Grid> </Grid>
</BaseBox> </BaseBox>

View File

@@ -1,3 +1,4 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { import {
BaseBox, BaseBox,
Grid, Grid,
@@ -7,11 +8,60 @@ import {
ViewWrapper, ViewWrapper,
} from "@/components"; } from "@/components";
import { MainColor } from "@/constants/color-palet"; import { MainColor } from "@/constants/color-palet";
import { apiDonationGetInvoiceById } from "@/service/api-client/api-donation";
import { GStyles } from "@/styles/global-styles"; import { GStyles } from "@/styles/global-styles";
import { dateTimeView } from "@/utils/dateTimeView";
import { formatCurrencyDisplay } from "@/utils/formatCurrencyDisplay";
import { FontAwesome6 } from "@expo/vector-icons"; import { FontAwesome6 } from "@expo/vector-icons";
import dayjs from "dayjs"; import { useFocusEffect, useLocalSearchParams } from "expo-router";
import { useCallback, useState } from "react";
export default function DonationSuccess() { export default function DonationSuccess() {
const { id, invoiceId } = useLocalSearchParams();
const [data, setData] = useState<any>(null);
useFocusEffect(
useCallback(() => {
onLoadData();
}, [id, invoiceId])
);
const onLoadData = async () => {
try {
const response = await apiDonationGetInvoiceById({
id: invoiceId as string,
});
console.log("[DATA]", JSON.stringify(response.data, null, 2));
setData(response.data);
} catch (error) {
console.log("[ERROR]", error);
}
};
const listData = [
{
label: "Bank",
value: (data && data?.MasterBank?.namaBank) || "-",
},
{
label: "Rekening Penerima",
value: (data && data?.MasterBank?.namaAkun) || "-",
},
{
label: "No Rekening",
value: (data && data?.MasterBank?.norek) || "-",
},
{
label: "Jumlah Donasi",
value: (data && formatCurrencyDisplay(data?.nominal)) || "-",
},
{
label: "Tanggal",
value: (data && dateTimeView({ date: data?.createdAt })) || "-",
},
];
return ( return (
<ViewWrapper> <ViewWrapper>
<StackCustom> <StackCustom>
@@ -59,25 +109,4 @@ export default function DonationSuccess() {
); );
} }
const listData = [
{
label: "Bank",
value: " BCA",
},
{
label: "Rekening Penerima",
value: "Himpunan Pengusaha Muda Indonesia",
},
{
label: "No Rekening",
value: "2304235678854332",
},
{
label: "Jumlah Donasi",
value: "Rp. 750.000",
},
{
label: "Tanggal",
value: `${dayjs(new Date()).format("DD/MM/YYYY")}`,
},
];

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

@@ -16,20 +16,19 @@ import Donation_ComponentInfoFundrising from "@/screens/Donation/ComponentInfoFu
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 { apiDonationGetOne } from "@/service/api-client/api-donation";
import { countDownAndCondition } from "@/utils/countDownAndCondition";
import { import {
router, router,
Stack, Stack,
useFocusEffect, useFocusEffect,
useLocalSearchParams, useLocalSearchParams,
} from "expo-router"; } from "expo-router";
import { useCallback, useState } from "react"; import { useCallback, useEffect, useState } from "react";
export default function DonasiDetailBeranda() { export default function DonasiDetailBeranda() {
const { user } = useAuth(); const { user } = useAuth();
const { id } = useLocalSearchParams(); const { id } = useLocalSearchParams();
console.log("ID ", id);
const [openDrawer, setOpenDrawer] = useState(false); const [openDrawer, setOpenDrawer] = useState(false);
const [data, setData] = useState<any>(); const [data, setData] = useState<any>();
useFocusEffect( useFocusEffect(
@@ -45,21 +44,41 @@ export default function DonasiDetailBeranda() {
category: "permanent", category: "permanent",
}); });
console.log("[RES GET ONE]", JSON.stringify(response.data, null, 2));
setData(response.data); setData(response.data);
} catch (error) { } catch (error) {
console.log("[ERROR]", 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
disabled={value?.reminder}
onPress={() => router.navigate(`/donation/${id}/(transaction-flow)`)} onPress={() => router.navigate(`/donation/${id}/(transaction-flow)`)}
> >
Donasi {value?.reminder ? "Waktu berakhir" : "Donasi"}
</ButtonCustom> </ButtonCustom>
</BoxButtonOnFooter> </BoxButtonOnFooter>
</> </>
@@ -80,8 +99,10 @@ export default function DonasiDetailBeranda() {
<ViewWrapper footerComponent={buttonSection}> <ViewWrapper footerComponent={buttonSection}>
<StackCustom> <StackCustom>
<Donation_ComponentBoxDetailData <Donation_ComponentBoxDetailData
sisaHari={value.sisa}
reminder={value.reminder}
data={data} data={data}
bottomSection={<Donation_ProgressSection id={id as string} />} bottomSection={<Donation_ProgressSection id={id as string} progres={Number(data?.progres) || 0} />}
/> />
<Donation_ComponentInfoFundrising dataAuthor={data?.Author} /> <Donation_ComponentInfoFundrising dataAuthor={data?.Author} />
<Donation_ComponentStoryFunrising <Donation_ComponentStoryFunrising

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

@@ -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

@@ -18,7 +18,7 @@ import {
import { apiMasterEventType } from "@/service/api-client/api-master"; import { apiMasterEventType } from "@/service/api-client/api-master";
import { DateTimePickerEvent } from "@react-native-community/datetimepicker"; import { DateTimePickerEvent } from "@react-native-community/datetimepicker";
import { router, useFocusEffect, useLocalSearchParams } from "expo-router"; import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
import React, { useCallback, useEffect, useState } from "react"; import { useCallback, useEffect, useState } from "react";
import Toast from "react-native-toast-message"; import Toast from "react-native-toast-message";
export default function EventEdit() { export default function EventEdit() {
@@ -55,6 +55,7 @@ export default function EventEdit() {
try { try {
setIsLoadData(true); setIsLoadData(true);
const response = await apiEventGetOne({ id: id as string }); const response = await apiEventGetOne({ id: id as string });
console.log("[DATA BY ID]", JSON.stringify(response, null, 2));
if (response.success) { if (response.success) {
setData(response.data); setData(response.data);
setSelectedDate(new Date(response.data.tanggal)); setSelectedDate(new Date(response.data.tanggal));
@@ -209,7 +210,7 @@ export default function EventEdit() {
minimumDate={new Date(Date.now())} minimumDate={new Date(Date.now())}
label="Tanggal & Waktu Mulai" label="Tanggal & Waktu Mulai"
required required
value={selectedDate as any} value={selectedDate}
onChange={(date: any) => { onChange={(date: any) => {
setSelectedDate(date as any); setSelectedDate(date as any);
}} }}
@@ -254,7 +255,6 @@ export default function EventEdit() {
placeholder="Masukkan deskripsi event" placeholder="Masukkan deskripsi event"
required required
showCount showCount
maxLength={100}
value={data?.deskripsi} value={data?.deskripsi}
onChangeText={(value) => setData({ ...data, deskripsi: value })} onChangeText={(value) => setData({ ...data, deskripsi: value })}
/> />

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

@@ -110,7 +110,7 @@ export default function EventCreate() {
const response = await apiEventCreate(newData); const response = await apiEventCreate(newData);
console.log("Response", JSON.stringify(response, null, 2)); console.log("Response", JSON.stringify(response, null, 2));
router.navigate("/event/status"); router.replace("/event/status");
} catch (error) { } catch (error) {
console.log(error); console.log(error);
} finally { } finally {
@@ -118,6 +118,7 @@ export default function EventCreate() {
} }
}; };
const buttonSubmit = ( const buttonSubmit = (
<ButtonCustom <ButtonCustom
isLoading={isLoading} isLoading={isLoading}
@@ -191,7 +192,7 @@ export default function EventCreate() {
placeholder="Masukkan deskripsi event" placeholder="Masukkan deskripsi event"
required required
showCount showCount
maxLength={100} value={data?.deskripsi || ""}
onChangeText={(value: any) => onChangeText={(value: any) =>
setData({ ...data, deskripsi: value }) setData({ ...data, deskripsi: value })
} }

View File

@@ -5,9 +5,12 @@ import {
TextAreaCustom, TextAreaCustom,
ViewWrapper, ViewWrapper,
} from "@/components"; } from "@/components";
import AlertWarning from "@/components/Alert/AlertWarning";
import { apiForumGetOne, apiForumUpdate } from "@/service/api-client/api-forum"; import { apiForumGetOne, apiForumUpdate } from "@/service/api-client/api-forum";
import { isBadContent } from "@/utils/badWordsIndonesia";
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 { Alert } from "react-native";
import Toast from "react-native-toast-message"; import Toast from "react-native-toast-message";
export default function ForumEdit() { export default function ForumEdit() {
@@ -43,6 +46,12 @@ export default function ForumEdit() {
}); });
return; return;
} }
if (isBadContent(text)) {
AlertWarning({});
return;
}
try { try {
setIsLoading(true); setIsLoading(true);
const response = await apiForumUpdate({ const response = await apiForumUpdate({

View File

@@ -1,142 +1,12 @@
/* eslint-disable react-hooks/exhaustive-deps */ /* eslint-disable react-hooks/exhaustive-deps */
import { import View_Forumku from "@/screens/Forum/ViewForumku";
AvatarComp, import View_Forumku2 from "@/screens/Forum/ViewForumku2";
ButtonCustom,
CenterCustom,
DrawerCustom,
FloatingButton,
Grid,
LoaderCustom,
StackCustom,
TextCustom,
ViewWrapper,
} from "@/components";
import { useAuth } from "@/hooks/use-auth";
import Forum_BoxDetailSection from "@/screens/Forum/DiscussionBoxSection";
import Forum_MenuDrawerBerandaSection from "@/screens/Forum/MenuDrawerSection.tsx/MenuBeranda";
import { apiForumGetAll } from "@/service/api-client/api-forum";
import { apiUser } from "@/service/api-client/api-user";
import { 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 { user } = useAuth();
const [openDrawer, setOpenDrawer] = useState(false);
const [status, setStatus] = useState("");
const [listData, setListData] = useState<any | null>(null);
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 {/* <View_Forumku /> */}
floatingButton={ <View_Forumku2 />
user?.id === id && (
<FloatingButton
onPress={() =>
router.navigate("/(application)/(user)/forum/create")
}
/>
)
}
>
<StackCustom>
<CenterCustom>
<AvatarComp
fileId={dataUser?.Profile?.imageId}
href={`/(application)/(image)/preview-image/${dataUser?.Profile?.imageId}`}
size="xl"
/>
</CenterCustom>
<Grid>
<Grid.Col span={6}>
<TextCustom bold truncate>
@{dataUser?.username || "-"}
</TextCustom>
<TextCustom>{listData?.length || "0"} postingan</TextCustom>
</Grid.Col>
<Grid.Col span={6} style={{ alignItems: "flex-end" }}>
<ButtonCustom href={`/profile/${dataUser?.Profile?.id}`}>
Kunjungi Profile
</ButtonCustom>
</Grid.Col>
</Grid>
{loadingGetList ? (
<LoaderCustom />
) : _.isEmpty(listData) ? (
<TextCustom> Tidak ada diskusi</TextCustom>
) : (
<>
{listData?.map((item: any, index: number) => (
<Forum_BoxDetailSection
isRightComponent={false}
key={index}
data={item}
isTruncate={true}
href={`/forum/${item.id}`}
onSetData={(value) => {
setOpenDrawer(value.setOpenDrawer);
setStatus(value.setStatus);
}}
/>
))}
</>
)}
</StackCustom>
</ViewWrapper>
{/* Drawer Komponen Eksternal */}
<DrawerCustom
height={"auto"}
isVisible={openDrawer}
closeDrawer={() => setOpenDrawer(false)}
>
<Forum_MenuDrawerBerandaSection
id={id as string}
status={status}
setIsDrawerOpen={() => {
setOpenDrawer(false);
}}
authorId={id as string}
/>
</DrawerCustom>
</> </>
); );
} }

View File

@@ -7,6 +7,7 @@ import {
TextCustom, TextCustom,
ViewWrapper, ViewWrapper,
} from "@/components"; } from "@/components";
import AlertWarning from "@/components/Alert/AlertWarning";
import { useAuth } from "@/hooks/use-auth"; 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";
@@ -18,9 +19,11 @@ import {
apiForumGetOne, apiForumGetOne,
apiForumUpdateStatus, apiForumUpdateStatus,
} from "@/service/api-client/api-forum"; } from "@/service/api-client/api-forum";
import { isBadContent } from "@/utils/badWordsIndonesia";
import { useFocusEffect, useLocalSearchParams } from "expo-router"; import { useFocusEffect, useLocalSearchParams } from "expo-router";
import _ from "lodash"; import _ from "lodash";
import { useCallback, useEffect, useState } from "react"; import { useCallback, useEffect, useState } from "react";
import { Alert } from "react-native";
interface CommentProps { interface CommentProps {
id: string; id: string;
@@ -110,11 +113,15 @@ export default function ForumDetail() {
// Create Commentar // Create Commentar
const handlerCreateCommentar = async () => { const handlerCreateCommentar = async () => {
if (isBadContent(text)) {
AlertWarning({});
return;
}
const newData = { const newData = {
comment: text, comment: text,
authorId: user?.id, authorId: user?.id,
}; };
try { try {
setLoadingComment(true); setLoadingComment(true);
const response = await apiForumCreateComment({ const response = await apiForumCreateComment({
@@ -223,6 +230,7 @@ export default function ForumDetail() {
> >
<Forum_MenuDrawerBerandaSection <Forum_MenuDrawerBerandaSection
id={dataId} id={dataId}
authorUsername={data?.Author?.username as string}
status={status} status={status}
setIsDrawerOpen={() => { setIsDrawerOpen={() => {
setOpenDrawer(false); setOpenDrawer(false);

View File

@@ -2,12 +2,15 @@ import {
BoxButtonOnFooter, BoxButtonOnFooter,
ButtonCustom, ButtonCustom,
TextAreaCustom, TextAreaCustom,
ViewWrapper, ViewWrapper
} from "@/components"; } from "@/components";
import AlertWarning from "@/components/Alert/AlertWarning";
import { useAuth } from "@/hooks/use-auth"; import { useAuth } from "@/hooks/use-auth";
import { apiForumCreate } from "@/service/api-client/api-forum"; import { apiForumCreate } from "@/service/api-client/api-forum";
import { isBadContent } from "@/utils/badWordsIndonesia";
import { router } from "expo-router"; import { router } from "expo-router";
import { useState } from "react"; import { useState } from "react";
import { Alert } from "react-native";
import Toast from "react-native-toast-message"; import Toast from "react-native-toast-message";
export default function ForumCreate() { export default function ForumCreate() {
@@ -16,11 +19,16 @@ export default function ForumCreate() {
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
const handlerSubmit = async () => { const handlerSubmit = async () => {
if (isBadContent(text)) {
AlertWarning({})
return;
}
const newData = { const newData = {
diskusi: text, diskusi: text,
authorId: user?.id, authorId: user?.id,
}; };
try { try {
setIsLoading(true); setIsLoading(true);
const response = await apiForumCreate({ data: newData }); const response = await apiForumCreate({ data: newData });

View File

@@ -1,129 +1,12 @@
/* eslint-disable react-hooks/exhaustive-deps */ /* eslint-disable react-hooks/exhaustive-deps */
import { import Forum_ViewBeranda from "@/screens/Forum/ViewBeranda";
AvatarComp, import Forum_ViewBeranda2 from "@/screens/Forum/ViewBeranda2";
BackButton,
DrawerCustom,
LoaderCustom,
SearchInput,
TextCustom,
ViewWrapper,
} from "@/components";
import FloatingButton from "@/components/Button/FloatingButton";
import { useAuth } from "@/hooks/use-auth";
import Forum_BoxDetailSection from "@/screens/Forum/DiscussionBoxSection";
import Forum_MenuDrawerBerandaSection from "@/screens/Forum/MenuDrawerSection.tsx/MenuBeranda";
import { apiForumGetAll } from "@/service/api-client/api-forum";
import { apiUser } from "@/service/api-client/api-user";
import { router, Stack, useFocusEffect } from "expo-router";
import _ from "lodash";
import { useCallback, useState } from "react";
export default function Forum() { export default function Forum() {
const [openDrawer, setOpenDrawer] = useState(false);
const [status, setStatus] = useState("");
const { user } = useAuth();
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 (
<> <>
<Stack.Screen {/* <Forum_ViewBeranda /> */}
options={{ <Forum_ViewBeranda2 />
title: "Forum",
headerLeft: () => <BackButton />,
headerRight: () => (
<AvatarComp
fileId={dataUser?.Profile?.imageId}
size="base"
href={`/forum/${user?.id}/forumku`}
/>
),
}}
/>
<ViewWrapper
headerComponent={
<SearchInput
placeholder="Cari topik diskusi"
onChangeText={(e) => setSearch(e)}
/>
}
floatingButton={
<FloatingButton
onPress={() =>
router.navigate("/(application)/(user)/forum/create")
}
/>
}
>
{loadingGetList ? (
<LoaderCustom />
) : _.isEmpty(listData) ? (
<TextCustom align="center" color="gray">
Tidak ada diskusi
</TextCustom>
) : (
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>
<DrawerCustom
height={"auto"}
isVisible={openDrawer}
closeDrawer={() => setOpenDrawer(false)}
>
<Forum_MenuDrawerBerandaSection
id={dataId}
authorId={authorId}
status={status}
setIsDrawerOpen={() => {
setOpenDrawer(false);
}}
/>
</DrawerCustom>
</> </>
); );
} }

View File

@@ -16,9 +16,10 @@ import { useEffect, useState } from "react";
export default function Application() { export default function Application() {
const { token, user } = useAuth(); const { token, user } = useAuth();
const [data, setData] = useState<any>(); const [data, setData] = useState<any>();
console.log("[User] >>", JSON.stringify(user?.id, null, 2));
useEffect(() => { useEffect(() => {
onLoadData(); onLoadData();
checkVersion(); checkVersion();
@@ -26,16 +27,24 @@ export default function Application() {
async function onLoadData() { async function onLoadData() {
const response = await apiUser(user?.id as string); const response = await apiUser(user?.id as string);
console.log("Response profile >>", JSON.stringify(response?.data?.Profile, null, 2)); console.log(
"[Profile ID]>>",
JSON.stringify(response?.data?.Profile.id, null, 2)
);
setData(response.data); setData(response.data);
} }
const checkVersion = async () => { const checkVersion = async () => {
const response = await apiVersion(); const response = await apiVersion();
console.log("Version >>", JSON.stringify(response.data, null, 2)); console.log("[Version] >>", JSON.stringify(response.data, null, 2));
}; };
if (user && user?.termsOfServiceAccepted === false) {
console.log("User is not accept term service");
return <Redirect href={`/terms-agreement`} />;
}
if (data && data?.active === false) { if (data && data?.active === false) {
console.log("User is not active"); console.log("User is not active");
return <Redirect href={`/waiting-room`} />; return <Redirect href={`/waiting-room`} />;

View File

@@ -1,23 +1,14 @@
import { import {
BaseBox,
FloatingButton, FloatingButton,
Grid,
LoaderCustom, LoaderCustom,
ProgressCustom, ViewWrapper
StackCustom,
TextCustom,
ViewWrapper,
} from "@/components"; } from "@/components";
import API_STRORAGE from "@/constants/base-url-api-strorage"; import NoDataText from "@/components/_ShareComponent/NoDataText";
import DUMMY_IMAGE from "@/constants/dummy-image-value"; import Investment_BoxBerandaSection from "@/screens/Invesment/BoxBerandaSection";
import { apiInvestmentGetAll } from "@/service/api-client/api-investment"; import { apiInvestmentGetAll } from "@/service/api-client/api-investment";
import { Ionicons } from "@expo/vector-icons";
import dayjs from "dayjs";
import { Image } from "expo-image";
import { router, useFocusEffect } from "expo-router"; import { router, useFocusEffect } from "expo-router";
import _ from "lodash"; import _ from "lodash";
import { useCallback, useState } from "react"; import { useCallback, useState } from "react";
import { View } from "react-native";
export default function InvestmentBursa() { export default function InvestmentBursa() {
const [list, setList] = useState<any[] | null>(null); const [list, setList] = useState<any[] | null>(null);
@@ -32,8 +23,10 @@ export default function InvestmentBursa() {
const onLoadList = async () => { const onLoadList = async () => {
try { try {
setLoadingList(true); setLoadingList(true);
const response = await apiInvestmentGetAll(); const response = await apiInvestmentGetAll({
console.log("[DATA LIST]", JSON.stringify(response.data, null, 2)); category: "bursa"
});
// console.log("[DATA LIST]", JSON.stringify(response.data, null, 2));
setList(response.data); setList(response.data);
} catch (error) { } catch (error) {
console.log("[ERROR]", error); console.log("[ERROR]", error);
@@ -52,95 +45,12 @@ export default function InvestmentBursa() {
{loadingList ? ( {loadingList ? (
<LoaderCustom /> <LoaderCustom />
) : _.isEmpty(list) ? ( ) : _.isEmpty(list) ? (
<TextCustom>Belum ada data</TextCustom> <NoDataText />
) : ( ) : (
list?.map((item: any, index: number) => ( list?.map((item: any, index: number) => (
<BaseBox <Investment_BoxBerandaSection id={item.id} data={item} key={index} />
key={index}
paddingTop={7}
paddingBottom={7}
href={`/investment/${item.id}`}
>
<Grid>
<Grid.Col span={5}>
<Image
source={
item && item.imageId
? API_STRORAGE.GET({ fileId: item.imageId })
: DUMMY_IMAGE.background
}
style={{ width: "auto", height: 100, borderRadius: 10 }}
/>
</Grid.Col>
<Grid.Col span={1}>
<View />
</Grid.Col>
<Grid.Col span={6}>
<StackCustom>
<TextCustom truncate={2}>{item.title}</TextCustom>
<ProgressCustom
label={`${item.progress}%`}
value={item.progress}
size="lg"
/>
{Number(item?.pencarianInvestor) -
dayjs().diff(dayjs(item.countDown), "days") <=
0 ? (
<View
style={{
flexDirection: "row",
alignItems: "center",
gap: 5,
}}
>
<Ionicons
name="alert-circle-outline"
size={16}
color="red"
/>
<TextCustom color="red" size="small">
Periode Investasi Selesai
</TextCustom>
</View>
) : (
<TextCustom>
Sisa waktu:{" "}
{Number(item?.pencarianInvestor) -
dayjs().diff(dayjs(item.countDown), "days")}{" "}
hari
</TextCustom>
)}
</StackCustom>
</Grid.Col>
</Grid>
</BaseBox>
)) ))
)} )}
</ViewWrapper> </ViewWrapper>
); );
} }
// <View style={{ padding: 20, gap: 16 }}>
// <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,50 +1,102 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { import {
BaseBox, BaseBox,
Grid, Grid,
LoaderCustom,
ProgressCustom, ProgressCustom,
Spacing, Spacing,
StackCustom, StackCustom,
TextCustom, TextCustom,
ViewWrapper, ViewWrapper,
} from "@/components"; } from "@/components";
import { router } from "expo-router"; import NoDataText from "@/components/_ShareComponent/NoDataText";
import { useAuth } from "@/hooks/use-auth";
import {
apiInvestmentGetAll
} from "@/service/api-client/api-investment";
import { formatCurrencyDisplay } from "@/utils/formatCurrencyDisplay";
import { router, useFocusEffect } from "expo-router";
import _ from "lodash";
import React, { useCallback, useState } from "react";
import { View } from "react-native"; import { View } from "react-native";
export default function InvestmentMyHolding() { export default function InvestmentMyHolding() {
const { user } = useAuth();
const [list, setList] = useState<any[] | null>(null);
const [loadingList, setLoadingList] = useState(false);
useFocusEffect(
useCallback(() => {
onLoadList();
}, [user?.id])
);
const onLoadList = async () => {
try {
setLoadingList(true);
const response = await apiInvestmentGetAll({
category: "my-holding",
authorId: user?.id,
});
console.log("[DATA LIST]", JSON.stringify(response.data, null, 2));
setList(response.data);
} catch (error) {
console.log("[ERROR]", error);
} finally {
setLoadingList(false);
}
};
return ( return (
<ViewWrapper hideFooter> <ViewWrapper hideFooter>
{Array.from({ length: 10 }).map((_, index) => ( {loadingList ? (
<BaseBox key={index} paddingTop={7} paddingBottom={7} onPress={() => router.push(`/investment/${index}/(my-holding)/holding-${index}`)}> <LoaderCustom />
<Grid> ) : _.isEmpty(list) ? (
<Grid.Col span={6}> <NoDataText />
<StackCustom gap={"xs"}> ) : (
<TextCustom truncate={2}> list?.map((item, index) => (
Title here : Lorem ipsum dolor sit amet consectetur <BaseBox
adipisicing elit. Omnis, exercitationem, sequi enim quod key={index}
distinctio maiores laudantium amet, quidem atque repellat sit paddingTop={7}
vitae qui aliquam est veritatis laborum eum voluptatum totam! paddingBottom={7}
</TextCustom> onPress={() =>
router.push(`/investment/${item?.id}/(my-holding)/${item?.id}`)
}
>
<Grid>
<Grid.Col span={6}>
<StackCustom gap={"xs"}>
<TextCustom truncate={2}>{item?.title}</TextCustom>
<Spacing height={5} /> <Spacing height={5} />
<TextCustom size="small">Rp. 7.500.000</TextCustom> <TextCustom size="small">
<TextCustom size="small">300 Lembar</TextCustom> Rp. {formatCurrencyDisplay(item?.nominal)}
</StackCustom> </TextCustom>
</Grid.Col> <TextCustom size="small">
<Grid.Col span={1}> {item?.lembarTerbeli} Lembar
<View /> </TextCustom>
</Grid.Col> </StackCustom>
<Grid.Col </Grid.Col>
span={5} <Grid.Col span={1}>
style={{ <View />
justifyContent: "center", </Grid.Col>
alignItems: "center", <Grid.Col
}} span={5}
> style={{
<ProgressCustom value={(index % 5) * 20} size="lg" /> justifyContent: "center",
</Grid.Col> alignItems: "center",
</Grid> }}
</BaseBox> >
))} <ProgressCustom
value={item?.progress}
label={`${item?.progress}%`}
size="lg"
/>
</Grid.Col>
</Grid>
</BaseBox>
))
)}
</ViewWrapper> </ViewWrapper>
); );
} }

View File

@@ -8,6 +8,7 @@ import {
TextCustom, TextCustom,
ViewWrapper, ViewWrapper,
} from "@/components"; } from "@/components";
import NoDataText from "@/components/_ShareComponent/NoDataText";
import { useAuth } from "@/hooks/use-auth"; import { useAuth } from "@/hooks/use-auth";
import { apiInvestmentGetInvoice } from "@/service/api-client/api-investment"; import { apiInvestmentGetInvoice } from "@/service/api-client/api-investment";
import { GStyles } from "@/styles/global-styles"; import { GStyles } from "@/styles/global-styles";
@@ -74,7 +75,7 @@ export default function InvestmentTransaction() {
{loadList ? ( {loadList ? (
<LoaderCustom /> <LoaderCustom />
) : _.isEmpty(list) ? ( ) : _.isEmpty(list) ? (
<TextCustom>Tidak ada data</TextCustom> <NoDataText/>
) : ( ) : (
list.map((item: any, i: number) => ( list.map((item: any, i: number) => (
<BaseBox <BaseBox

View File

@@ -1,29 +1,56 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { import {
BackButton, BackButton,
BaseBox, BaseBox,
DotButton, DotButton,
DrawerCustom, DrawerCustom,
Grid, Grid,
MenuDrawerDynamicGrid, MenuDrawerDynamicGrid,
StackCustom, StackCustom,
TextCustom, TextCustom,
ViewWrapper, ViewWrapper,
} from "@/components"; } from "@/components";
import { IconDocument, IconEdit, IconNews } from "@/components/_Icon"; 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 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 { apiInvestmentGetInvoice } from "@/service/api-client/api-investment";
import { formatCurrencyDisplay } from "@/utils/formatCurrencyDisplay";
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 InvestmentDetailHolding() { export default function InvestmentDetailHolding() {
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 apiInvestmentGetInvoice({
id: id as string,
authorId: user?.id,
category: "invoice",
});
console.log("[DATA]", JSON.stringify(response.data, null, 2));
setData(response.data);
} catch (error) {
console.log("[ERROR]", error);
}
};
const handlePressDraft = (item: IMenuDrawerItem) => { const handlePressDraft = (item: IMenuDrawerItem) => {
console.log("PATH >> ", item.path); console.log("PATH >> ", item.path);
@@ -39,7 +66,8 @@ export default function InvestmentDetailHolding() {
const bottomSection = ( const bottomSection = (
<Invesment_ComponentBoxOnBottomDetail <Invesment_ComponentBoxOnBottomDetail
id={id as string} prospectusId={id as string}
id={data?.Investasi?.id as string}
status={"publish"} status={"publish"}
/> />
); );
@@ -64,10 +92,12 @@ export default function InvestmentDetailHolding() {
<StackCustom gap={"xs"}> <StackCustom gap={"xs"}>
<Grid> <Grid>
<Grid.Col span={6}> <Grid.Col span={6}>
<TextCustom bold>Nila Transaksi</TextCustom> <TextCustom bold>Nilai Transaksi</TextCustom>
</Grid.Col> </Grid.Col>
<Grid.Col span={6}> <Grid.Col span={6}>
<TextCustom bold>Rp. 7.500.000</TextCustom> <TextCustom bold>
Rp. {data ? formatCurrencyDisplay(data?.nominal) : ""}
</TextCustom>
</Grid.Col> </Grid.Col>
</Grid> </Grid>
<Grid> <Grid>
@@ -75,12 +105,16 @@ export default function InvestmentDetailHolding() {
<TextCustom bold>Saham Terbeli</TextCustom> <TextCustom bold>Saham Terbeli</TextCustom>
</Grid.Col> </Grid.Col>
<Grid.Col span={6}> <Grid.Col span={6}>
<TextCustom bold>300 Lembar</TextCustom> <TextCustom bold>
{data ? data?.lembarTerbeli : ""} Lembar
</TextCustom>
</Grid.Col> </Grid.Col>
</Grid> </Grid>
</StackCustom> </StackCustom>
</BaseBox> </BaseBox>
<Invesment_DetailDataPublishSection <Invesment_DetailDataPublishSection
data={data && data?.Investasi}
status={"publish"} status={"publish"}
bottomSection={bottomSection} bottomSection={bottomSection}
/> />

View File

@@ -1,9 +1,73 @@
import { BaseBox, Grid, Spacing, StackCustom, TextCustom, ViewWrapper } from "@/components"; /* eslint-disable react-hooks/exhaustive-deps */
import {
BaseBox,
Grid,
Spacing,
StackCustom,
TextCustom,
ViewWrapper,
} from "@/components";
import { MainColor } from "@/constants/color-palet"; import { MainColor } from "@/constants/color-palet";
import { apiInvestmentGetInvoice } from "@/service/api-client/api-investment";
import { GStyles } from "@/styles/global-styles"; import { GStyles } from "@/styles/global-styles";
import { dateTimeView } from "@/utils/dateTimeView";
import { formatCurrencyDisplay } from "@/utils/formatCurrencyDisplay";
import { FontAwesome6 } from "@expo/vector-icons"; import { FontAwesome6 } from "@expo/vector-icons";
import { useLocalSearchParams, useFocusEffect } from "expo-router";
import React from "react";
export default function InvestmentFailed() { export default function InvestmentFailed() {
const { id } = useLocalSearchParams();
console.log("[ID]", id);
const [data, setData] = React.useState<any | null>(null);
useFocusEffect(
React.useCallback(() => {
onLoadData();
}, [id])
);
const onLoadData = async () => {
try {
const response = await apiInvestmentGetInvoice({
id: id as string,
category: "invoice",
});
console.log("[RES INVOICE]", JSON.stringify(response.data, null, 2));
setData(response.data);
} catch (error) {
console.log("[ERROR]", error);
}
};
const listData = [
{
label: "Bank",
value: (data && data?.MasterBank?.namaBank) || "-",
},
{
label: "Rekening Penerima",
value: (data && data?.MasterBank?.namaAkun) || "-",
},
{
label: "No Rekening",
value: (data && data?.MasterBank?.norek) || "-",
},
{
label: "Jumlah",
value: `Rp ${data && formatCurrencyDisplay(data?.nominal)}` || "-",
},
{
label: "Tanggal",
value: (data && dateTimeView({ date: data?.createdAt })) || "-",
},
{
label: "Lembar Terbeli",
value: (data && formatCurrencyDisplay(data?.lembarTerbeli)) || "-",
},
];
return ( return (
<ViewWrapper> <ViewWrapper>
<StackCustom> <StackCustom>
@@ -11,8 +75,7 @@ export default function InvestmentFailed() {
<StackCustom> <StackCustom>
<TextCustom bold align="center"> <TextCustom bold align="center">
Transaksi anda gagal karena bukti transfer tidak sesuai dengan Transaksi anda gagal karena bukti transfer tidak sesuai dengan
data kami. Jika ini masalah khusus silahkan hubungi pada kontak data kami. Hubungi admin untuk memperbaiki masalah ini.
whatsapp kami.
</TextCustom> </TextCustom>
<FontAwesome6 <FontAwesome6
@@ -50,30 +113,3 @@ export default function InvestmentFailed() {
</ViewWrapper> </ViewWrapper>
); );
} }
const listData = [
{
label: "Bank",
value: " BCA",
},
{
label: "Rekening Penerima",
value: "Himpunan Pengusaha Muda Indonesia",
},
{
label: "No Rekening",
value: "2304235678854332",
},
{
label: "Jumlah",
value: "Rp. 1.000.000",
},
{
label: "Tanggal",
value: "2022-01-01",
},
{
label: "Lembar Terbeli",
value: "100",
},
];

View File

@@ -108,7 +108,9 @@ 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>{data?.sisaLembar || "-"}</TextCustom> <TextCustom>
{data && formatCurrencyDisplay(data?.sisaLembar) || "-"}
</TextCustom>
</Grid.Col> </Grid.Col>
</Grid> </Grid>
<Grid> <Grid>
@@ -116,7 +118,9 @@ 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>{data?.hargaLembar || "-"}</TextCustom> <TextCustom>
{data && formatCurrencyDisplay(data?.hargaLembar) || "-"}
</TextCustom>
</Grid.Col> </Grid.Col>
</Grid> </Grid>
<Grid> <Grid>

View File

@@ -1,3 +1,4 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { import {
BaseBox, BaseBox,
Grid, Grid,
@@ -7,10 +8,66 @@ import {
ViewWrapper, ViewWrapper,
} from "@/components"; } from "@/components";
import { MainColor } from "@/constants/color-palet"; import { MainColor } from "@/constants/color-palet";
import { apiInvestmentGetInvoice } from "@/service/api-client/api-investment";
import { GStyles } from "@/styles/global-styles"; import { GStyles } from "@/styles/global-styles";
import { dateTimeView } from "@/utils/dateTimeView";
import { formatCurrencyDisplay } from "@/utils/formatCurrencyDisplay";
import { FontAwesome6 } from "@expo/vector-icons"; import { FontAwesome6 } from "@expo/vector-icons";
import { useFocusEffect, useLocalSearchParams } from "expo-router";
import React from "react";
export default function InvestmentSuccess() { export default function InvestmentSuccess() {
const { id } = useLocalSearchParams();
console.log("[ID]", id);
const [data, setData] = React.useState<any | null>(null);
useFocusEffect(
React.useCallback(() => {
onLoadData();
}, [id])
);
const onLoadData = async () => {
try {
const response = await apiInvestmentGetInvoice({
id: id as string,
category: "invoice",
});
console.log("[RES INVOICE]", JSON.stringify(response.data, null, 2));
setData(response.data);
} catch (error) {
console.log("[ERROR]", error);
}
};
const listData = [
{
label: "Bank",
value: (data && data?.MasterBank?.namaBank) || "-",
},
{
label: "Rekening Penerima",
value: (data && data?.MasterBank?.namaAkun) || "-",
},
{
label: "No Rekening",
value: (data && data?.MasterBank?.norek) || "-",
},
{
label: "Jumlah",
value: `Rp ${data && formatCurrencyDisplay(data?.nominal)}` || "-",
},
{
label: "Tanggal",
value: (data && dateTimeView({ date: data?.createdAt })) || "-",
},
{
label: "Lembar Terbeli",
value: (data && formatCurrencyDisplay(data?.lembarTerbeli)) || "-",
},
];
return ( return (
<ViewWrapper> <ViewWrapper>
<StackCustom> <StackCustom>
@@ -35,8 +92,7 @@ export default function InvestmentSuccess() {
Detail Transaksi Detail Transaksi
</TextCustom> </TextCustom>
<Spacing/> <Spacing />
<StackCustom> <StackCustom>
{listData.map((item, i) => ( {listData.map((item, i) => (
@@ -45,7 +101,9 @@ export default function InvestmentSuccess() {
<TextCustom bold>{item.label}</TextCustom> <TextCustom bold>{item.label}</TextCustom>
</Grid.Col> </Grid.Col>
<Grid.Col span={7}> <Grid.Col span={7}>
<TextCustom style={{paddingLeft: 10}}>{item.value}</TextCustom> <TextCustom style={{ paddingLeft: 10 }}>
{item.value}
</TextCustom>
</Grid.Col> </Grid.Col>
</Grid> </Grid>
))} ))}
@@ -55,30 +113,3 @@ export default function InvestmentSuccess() {
</ViewWrapper> </ViewWrapper>
); );
} }
const listData = [
{
label: "Bank",
value: " BCA",
},
{
label: "Rekening Penerima",
value: "Himpunan Pengusaha Muda Indonesia",
},
{
label: "No Rekening",
value: "2304235678854332",
},
{
label: "Jumlah",
value: "Rp. 1.000.000",
},
{
label: "Tanggal",
value: "2022-01-01",
},
{
label: "Lembar Terbeli",
value: "100",
},
];

View File

@@ -15,6 +15,7 @@ import Investment_ButtonInvestasiSection from "@/screens/Invesment/ButtonInvesta
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 { apiInvestmentGetOne } from "@/service/api-client/api-investment";
import { countDownAndCondition } from "@/utils/countDownAndCondition";
import { AntDesign, MaterialIcons } from "@expo/vector-icons"; import { AntDesign, MaterialIcons } from "@expo/vector-icons";
import { import {
router, router,
@@ -23,7 +24,7 @@ import {
useLocalSearchParams, useLocalSearchParams,
} from "expo-router"; } from "expo-router";
import _ from "lodash"; import _ from "lodash";
import { useCallback, useState } from "react"; import { useCallback, useEffect, useState } from "react";
export default function InvestmentDetail() { export default function InvestmentDetail() {
const { user } = useAuth(); const { user } = useAuth();
@@ -62,6 +63,31 @@ export default function InvestmentDetail() {
setOpenDrawerPublish(false); setOpenDrawerPublish(false);
}; };
const [value, setValue] = useState({
sisa: 0,
reminder: false,
});
useEffect(() => {
updateCountDown();
}, [data]);
console.log("[DATA DETAIL]", JSON.stringify(data, null, 2));
const updateCountDown = () => {
const countDown = countDownAndCondition({
duration: data?.MasterPencarianInvestor.name,
publishTime: data?.countDown,
});
setValue({
sisa: countDown.durationDay,
reminder: countDown.reminder,
});
};
const bottomSection = ( const bottomSection = (
<Invesment_ComponentBoxOnBottomDetail <Invesment_ComponentBoxOnBottomDetail
id={id as string} id={id as string}
@@ -71,7 +97,7 @@ export default function InvestmentDetail() {
); );
const buttonSection = ( const buttonSection = (
<Investment_ButtonInvestasiSection id={id as string} isMine={user?.id === data?.author?.id} /> <Investment_ButtonInvestasiSection id={id as string} isMine={user?.id === data?.author?.id} reminder={value.reminder} />
); );
return ( return (

View File

@@ -1,20 +1,66 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { import {
AvatarUsernameAndOtherComponent, AvatarUsernameAndOtherComponent,
BoxWithHeaderSection, BoxWithHeaderSection,
LoaderCustom,
TextCustom, TextCustom,
ViewWrapper, ViewWrapper,
} from "@/components"; } from "@/components";
import NoDataText from "@/components/_ShareComponent/NoDataText";
import { apiInvestmentGetInvestorById } from "@/service/api-client/api-investment";
import { formatCurrencyDisplay } from "@/utils/formatCurrencyDisplay";
import { useFocusEffect, useLocalSearchParams } from "expo-router";
import _ from "lodash";
import { useCallback, useState } from "react";
export default function InvestmentInvestor() { export default function InvestmentInvestor() {
const { id } = useLocalSearchParams();
const [list, setList] = useState<any[] | null>(null);
const [loadingList, setLoadingList] = useState(false);
useFocusEffect(
useCallback(() => {
onLoadList();
}, [id])
);
const onLoadList = async () => {
try {
setLoadingList(true);
const response = await apiInvestmentGetInvestorById({
id: id as string,
})
console.log("[DATA LIST]", JSON.stringify(response.data, null, 2));
setList(response.data);
} catch (error) {
console.log("[ERROR]", error);
} finally {
setLoadingList(false);
}
}
return ( return (
<> <>
<ViewWrapper> <ViewWrapper>
{Array.from({ length: 10 }).map((_, index) => ( {loadingList ? (
<BoxWithHeaderSection key={index}> <LoaderCustom />
<AvatarUsernameAndOtherComponent /> ) : _.isEmpty(list) ? (
<TextCustom bold>Rp. 7.000.000</TextCustom> <NoDataText />
</BoxWithHeaderSection> ) : (
))} list?.map((item: any, index: number) => (
<BoxWithHeaderSection key={index}>
<AvatarUsernameAndOtherComponent
avatar={item?.Author?.Profile?.imageId}
name={item?.Author?.username}
avatarHref={`/profile/${item?.Author?.Profile?.id}`}
/>
<TextCustom bold>
Rp. {formatCurrencyDisplay(item?.nominal)}
</TextCustom>
</BoxWithHeaderSection>
))
)}
</ViewWrapper> </ViewWrapper>
</> </>
); );

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,32 +1,6 @@
import { import MapsView from "@/screens/Maps/MapsView";
ButtonCustom, import MapsView2 from "@/screens/Maps/MapsView2";
DrawerCustom, import { Text, View } from "react-native";
DummyLandscapeImage,
Grid,
Spacing,
StackCustom,
TextCustom,
ViewWrapper,
} from "@/components";
import GridTwoView from "@/components/_ShareComponent/GridTwoView";
import API_IMAGE from "@/constants/api-storage";
import { ICON_SIZE_SMALL } from "@/constants/constans-value";
import { apiMapsGetAll } from "@/service/api-client/api-maps";
import { openInDeviceMaps } from "@/utils/openInDeviceMaps";
import { FontAwesome, Ionicons } from "@expo/vector-icons";
import { Image } from "expo-image";
import { router, useFocusEffect } from "expo-router";
import { useCallback, useState } from "react";
import { View } from "react-native";
import MapView, { Marker } from "react-native-maps";
const defaultRegion = {
latitude: -8.737109,
longitude: 115.1756897,
latitudeDelta: 0.1,
longitudeDelta: 0.1,
height: 300,
};
export interface LocationItem { export interface LocationItem {
id: string | number; id: string | number;
@@ -37,198 +11,11 @@ export interface LocationItem {
} }
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 }}> <MapsView />
{/* <MapCustom height={"100%"} /> */} {/* <MapsView2 />, */}
<View style={{ flex: 1 }}> {/* <View style={{ flex: 1, backgroundColor: "gray" }}><Text style={{ color: "white" }}>Map disabled</Text></View> */}
{loadList ? (
<MapView
initialRegion={defaultRegion}
style={{
width: "100%",
height: "100%",
}}
/>
) : (
<MapView
initialRegion={defaultRegion}
style={{
width: "100%",
height: "100%",
}}
>
{list?.map((item: any, index: number) => {
return (
<Marker
key={item?.id}
coordinate={{
latitude: item?.latitude,
longitude: item?.longitude,
}}
title={item?.namePin}
onPress={() => {
setOpenDrawer(true);
setSelected({
id: item?.id,
bidangBisnis:
item?.Portofolio?.MasterBidangBisnis?.name,
nomorTelepon: item?.Portofolio?.tlpn,
alamatBisnis: item?.Portofolio?.alamatKantor,
namePin: item?.namePin,
imageId: item?.imageId,
portofolioId: item?.Portofolio?.id,
latitude: item?.latitude,
longitude: item?.longitude,
});
}}
// Gunakan gambar kustom jika tersedia
>
<View style={{}}>
<Image
source={{
uri: API_IMAGE.GET({
fileId: item?.Portofolio?.logoId,
}),
}}
style={{
width: 30,
height: 30,
borderRadius: 100,
borderWidth: 1,
}}
/>
</View>
</Marker>
);
})}
</MapView>
)}
</View>
</ViewWrapper>
<DrawerCustom
isVisible={openDrawer}
closeDrawer={() => setOpenDrawer(false)}
height={"auto"}
>
<DummyLandscapeImage height={200} imageId={selected.imageId} />
<Spacing />
<StackCustom gap={"xs"}>
<GridTwoView
spanLeft={2}
spanRight={10}
leftIcon={
<FontAwesome
name="building-o"
size={ICON_SIZE_SMALL}
color="white"
/>
}
rightIcon={<TextCustom>{selected.namePin}</TextCustom>}
/>
<GridTwoView
spanLeft={2}
spanRight={10}
leftIcon={
<Ionicons
name="list-outline"
size={ICON_SIZE_SMALL}
color="white"
/>
}
rightIcon={<TextCustom>{selected.bidangBisnis}</TextCustom>}
/>
<GridTwoView
spanLeft={2}
spanRight={10}
leftIcon={
<Ionicons
name="call-outline"
size={ICON_SIZE_SMALL}
color="white"
/>
}
rightIcon={<TextCustom>{selected.nomorTelepon}</TextCustom>}
/>
<GridTwoView
spanLeft={2}
spanRight={10}
leftIcon={
<Ionicons
name="location-outline"
size={ICON_SIZE_SMALL}
color="white"
/>
}
rightIcon={<TextCustom>{selected.alamatBisnis}</TextCustom>}
/>
<Grid>
<Grid.Col span={6} style={{ paddingRight: 10 }}>
<ButtonCustom
onPress={() => {
setOpenDrawer(false);
router.push(`/portofolio/${selected.portofolioId}`);
}}
>
Detail
</ButtonCustom>
</Grid.Col>
<Grid.Col span={6} style={{ paddingLeft: 10 }}>
<ButtonCustom
onPress={() => {
openInDeviceMaps({
latitude: selected.latitude,
longitude: selected.longitude,
title: selected.namePin,
});
}}
>
Buka Maps
</ButtonCustom>
</Grid.Col>
</Grid>
</StackCustom>
</DrawerCustom>
</> </>
); );
} }

View File

@@ -0,0 +1,148 @@
import {
AvatarUsernameAndOtherComponent,
BadgeCustom,
ClickableCustom,
Divider,
SelectCustom,
TextCustom,
} from "@/components";
import ListEmptyComponent from "@/components/_ShareComponent/ListEmptyComponent";
import ListLoaderFooterComponent from "@/components/_ShareComponent/ListLoaderFooterComponent";
import ListSkeletonComponent from "@/components/_ShareComponent/ListSkeletonComponent";
import NewWrapper from "@/components/_ShareComponent/NewWrapper";
import { MainColor } from "@/constants/color-palet";
import { useAuth } from "@/hooks/use-auth";
import { usePaginatedApi } from "@/hooks/use-paginated-api";
import { apiGetBlocked } from "@/service/api-client/api-blocked";
import { apiMasterAppCategory } from "@/service/api-client/api-master";
import { router, useFocusEffect } from "expo-router";
import _ from "lodash";
import { useCallback, useEffect, useRef, useState } from "react";
import { RefreshControl, View } from "react-native";
const PAGE_SIZE = 10;
export default function ProfileBlockedList() {
const { user } = useAuth();
const [masterApp, setMasterApp] = useState<any[]>([]);
const isInitialMount = useRef(true);
const {
data: listData,
loading,
refreshing,
hasMore,
search,
setSearch,
onRefresh,
loadMore,
} = usePaginatedApi({
fetcher: async (params: { page: number; search?: string }) => {
const response = await apiGetBlocked({
id: user?.id as any,
search: search,
page: String(params.page) as any,
});
return response.data;
},
initialSearch: "",
pageSize: PAGE_SIZE,
dependencies: [user?.id],
});
useEffect(() => {
fetchMasterApp();
}, []);
// 🔁 Refresh otomatis saat kembali ke halaman ini
useFocusEffect(
useCallback(() => {
if (isInitialMount.current) {
// Skip saat pertama kali mount
isInitialMount.current = false;
return;
}
// Hanya refresh saat kembali dari screen lain
onRefresh();
}, [onRefresh])
);
const fetchMasterApp = async () => {
const response = await apiMasterAppCategory();
setMasterApp(response.data);
};
const renderHeader = () => (
<SelectCustom
placeholder="Pilih Kategori Fitur"
data={masterApp.map((item) => ({
label: item.name,
value: item.id,
}))}
value={search === "" ? undefined : search}
onChange={(value) => {
setSearch(value as any);
}}
/>
);
const renderItem = ({ item }: { item: any }) => (
<>
<ClickableCustom
onPress={() => {
router.push(`/profile/${item.id}/detail-blocked`);
}}
>
<View
style={{
paddingInline: 8,
}}
>
<AvatarUsernameAndOtherComponent
avatarHref={`/profile/${item?.blocked?.Profile?.id}`}
avatar={item?.blocked?.Profile?.imageId}
name={item?.blocked?.username}
rightComponent={
<View style={{ flexDirection: "row", gap: 4 }}>
<BadgeCustom>
<TextCustom size={"small"} bold truncate>
{item?.menuFeature?.name}
</TextCustom>
</BadgeCustom>
</View>
}
/>
<Divider color="gray" />
</View>
</ClickableCustom>
</>
);
return (
<>
<NewWrapper
// headerComponent={renderHeader()}
listData={listData}
renderItem={renderItem}
onEndReached={loadMore}
refreshControl={
<RefreshControl
progressBackgroundColor={MainColor.yellow}
refreshing={refreshing}
onRefresh={onRefresh}
/>
}
ListFooterComponent={
hasMore && !refreshing ? <ListLoaderFooterComponent /> : null
}
ListEmptyComponent={
!loading && _.isEmpty(listData) ? (
<ListSkeletonComponent />
) : (
<ListEmptyComponent />
)
}
/>
</>
);
}

View File

@@ -0,0 +1,93 @@
import {
AlertDefaultSystem,
AvatarUsernameAndOtherComponent,
BaseBox,
BoxButtonOnFooter,
BoxWithHeaderSection,
ButtonCustom,
NewWrapper,
StackCustom,
TextCustom,
} from "@/components";
import AvatarAndBackground from "@/screens/Profile/AvatarAndBackground";
import {
apiGetBlockedById,
apiUnblock,
} from "@/service/api-client/api-blocked";
import { router, useLocalSearchParams } from "expo-router";
import _ from "lodash";
import { useEffect, useState } from "react";
export default function ProfileDetailBlocked() {
const { id } = useLocalSearchParams();
const [data, setData] = useState<any>(null);
const [isLoading, setIsLoading] = useState<boolean>(false);
useEffect(() => {
fetchData();
}, [id]);
const fetchData = async () => {
const response = await apiGetBlockedById({ id: String(id) });
// console.log("[RESPONSE >>]", JSON.stringify(response, null, 2));
setData(response.data);
};
const handleSubmit = async () => {
try {
setIsLoading(true);
await apiUnblock({ id: String(id) });
router.back();
} catch (error) {
console.log("[ERROR >>]", JSON.stringify(error, null, 2));
} finally {
setIsLoading(false);
}
};
return (
<>
<NewWrapper
footerComponent={
<BoxButtonOnFooter>
<ButtonCustom
isLoading={isLoading}
onPress={() => {
AlertDefaultSystem({
title: "Buka Blokir",
message: "Apakah anda yakin ingin membuka blokir ini?",
textLeft: "Tidak",
textRight: "Ya",
onPressRight: () => {
handleSubmit();
},
});
}}
>
Buka Blokir
</ButtonCustom>
</BoxButtonOnFooter>
}
>
<BoxWithHeaderSection>
<StackCustom>
<AvatarUsernameAndOtherComponent
avatarHref={`/profile/${data?.blocked?.Profile?.id}`}
avatar={data?.blocked?.Profile?.imageId}
name={data?.blocked?.username}
/>
<TextCustom align="center">
Jika anda membuka blokir ini maka semua postingan terkait user ini
akan muncul kembali di beranda
<TextCustom bold color="red">
{" "}
{_.upperCase(data?.menuFeature?.name)}
</TextCustom>
</TextCustom>
</StackCustom>
</BoxWithHeaderSection>
</NewWrapper>
</>
);
}

View File

@@ -64,14 +64,18 @@ export default function Profile() {
}; };
const onLoadPortofolio = async (id: string) => { const onLoadPortofolio = async (id: string) => {
const response = await apiGetPortofolio({ id: id }); try {
const lastTwoByDate = response.data const response = await apiGetPortofolio({ id: id });
.sort( const lastTwoByDate = response.data
(a: any, b: any) => .sort(
new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime() (a: any, b: any) =>
) // urut desc new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()
.slice(0, 2); ) // urut desc
setListPortofolio(lastTwoByDate); .slice(0, 2);
setListPortofolio(lastTwoByDate);
} catch (error) {
console.log("[ERROR]", error);
}
}; };
return ( return (

View File

@@ -33,6 +33,16 @@ export default function ProfileLayout() {
name="create" name="create"
options={{ title: "Buat Profile", headerBackVisible: false }} options={{ title: "Buat Profile", headerBackVisible: false }}
/> />
<Stack.Screen
name="[id]/blocked-list"
options={{ title: "Blocked List", headerLeft: () => <BackButton /> }}
/>
<Stack.Screen
name="[id]/detail-blocked"
options={{ title: "Detail Blokir", headerLeft: () => <BackButton /> }}
/>
</Stack> </Stack>
</> </>
); );

View File

@@ -6,6 +6,7 @@ import {
TextCustom, TextCustom,
ViewWrapper, ViewWrapper,
} from "@/components"; } from "@/components";
import { useAuth } from "@/hooks/use-auth";
import Voting_BoxPublishSection from "@/screens/Voting/BoxPublishSection"; import Voting_BoxPublishSection from "@/screens/Voting/BoxPublishSection";
import { apiVotingGetAll } from "@/service/api-client/api-voting"; import { apiVotingGetAll } from "@/service/api-client/api-voting";
import { router, useFocusEffect } from "expo-router"; import { router, useFocusEffect } from "expo-router";
@@ -13,6 +14,7 @@ import _ from "lodash";
import { useCallback, useState } from "react"; import { useCallback, useState } from "react";
export default function VotingBeranda() { export default function VotingBeranda() {
const { user } = useAuth();
const [listData, setListData] = useState<any>([]); const [listData, setListData] = useState<any>([]);
const [loadingGetData, setLoadingGetData] = useState(false); const [loadingGetData, setLoadingGetData] = useState(false);
const [search, setSearch] = useState(""); const [search, setSearch] = useState("");
@@ -29,6 +31,7 @@ export default function VotingBeranda() {
const response = await apiVotingGetAll({ const response = await apiVotingGetAll({
search, search,
category: "beranda", category: "beranda",
userLoginId: user?.id,
}); });
if (response.success) { if (response.success) {
setListData(response.data); setListData(response.data);

View File

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

View File

@@ -4,6 +4,7 @@ import {
ButtonCenteredOnly, ButtonCenteredOnly,
ButtonCustom, ButtonCustom,
InformationBox, InformationBox,
NewWrapper,
StackCustom, StackCustom,
ViewWrapper, ViewWrapper,
} from "@/components"; } from "@/components";
@@ -12,6 +13,7 @@ import { useAuth } from "@/hooks/use-auth";
import { apiUser } from "@/service/api-client/api-user"; import { apiUser } from "@/service/api-client/api-user";
import { Ionicons } from "@expo/vector-icons"; import { Ionicons } from "@expo/vector-icons";
import { router } from "expo-router"; import { router } from "expo-router";
import { RefreshControl } from "react-native";
import Toast from "react-native-toast-message"; import Toast from "react-native-toast-message";
export default function WaitingRoom() { export default function WaitingRoom() {
@@ -33,7 +35,7 @@ export default function WaitingRoom() {
} else { } else {
Toast.show({ Toast.show({
type: "success", type: "success",
text1: "Akun anda telah aktif", // text2: "Anda berhasil login", text1: "Selamat ! Akun anda telah aktif", // text2: "Anda berhasil login",
}); });
router.replace(`/(application)/(user)/profile/create`); router.replace(`/(application)/(user)/profile/create`);
} }
@@ -82,10 +84,18 @@ export default function WaitingRoom() {
return ( return (
<> <>
<ViewWrapper footerComponent={logoutButton()}> <NewWrapper
footerComponent={logoutButton()}
refreshControl={
<RefreshControl refreshing={isLoading} onRefresh={handleCheck} />
}
>
<StackCustom> <StackCustom>
<InformationBox text="Permohonan akses Anda sedang dalam proses verifikasi oleh admin. Harap tunggu, Anda akan menerima pemberitahuan melalui Whatsapp setelah disetujui." /> <InformationBox
<ButtonCenteredOnly text="Akun Anda sedang menunggu aktivasi.
Silakan tunggu beberapa saat. Untuk memperbarui status, tarik layar ke bawah."
/>
{/* <ButtonCenteredOnly
isLoading={isLoading} isLoading={isLoading}
onPress={() => { onPress={() => {
handleCheck(); handleCheck();
@@ -93,9 +103,9 @@ export default function WaitingRoom() {
icon="refresh-ccw" icon="refresh-ccw"
> >
Check Check
</ButtonCenteredOnly> </ButtonCenteredOnly> */}
</StackCustom> </StackCustom>
</ViewWrapper> </NewWrapper>
</> </>
); );
} }

View File

@@ -10,6 +10,7 @@ export default function ApplicationLayout() {
<Stack.Screen name="admin" options={{ headerShown: false }} /> <Stack.Screen name="admin" options={{ headerShown: false }} />
{/* Take Picture */} {/* Take Picture */}
<Stack.Screen <Stack.Screen
name="(image)/take-picture/[id]/index" name="(image)/take-picture/[id]/index"

View File

@@ -22,8 +22,6 @@ export default function AdminCollaboration() {
category: "dashboard", category: "dashboard",
}); });
console.log("[RESPONSE]", JSON.stringify(response, null, 2));
if (response.success) { if (response.success) {
setList(response.data); setList(response.data);
} }
@@ -46,7 +44,6 @@ export default function AdminCollaboration() {
} }
const listData = (list: any) => { const listData = (list: any) => {
console.log("[LIST masuk]", JSON.stringify(list, null, 2));
return [ return [
{ {
label: "Publish", label: "Publish",

View File

@@ -1,3 +1,4 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { import {
ActionIcon, ActionIcon,
AlertDefaultSystem, AlertDefaultSystem,
@@ -17,98 +18,154 @@ import { IconDot, IconList } from "@/components/_Icon/IconComponent";
import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButtonAntTitle"; import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButtonAntTitle";
import AdminButtonReject from "@/components/_ShareComponent/Admin/ButtonReject"; import AdminButtonReject from "@/components/_ShareComponent/Admin/ButtonReject";
import AdminButtonReview from "@/components/_ShareComponent/Admin/ButtonReview"; import AdminButtonReview from "@/components/_ShareComponent/Admin/ButtonReview";
import { GridDetail_4_8 } from "@/components/_ShareComponent/GridDetail_4_8"; import { GridSpan_4_8 } from "@/components/_ShareComponent/GridSpan_4_8";
import { MainColor } from "@/constants/color-palet"; import ReportBox from "@/components/Box/ReportBox";
import { ICON_SIZE_BUTTON, TEXT_SIZE_LARGE } from "@/constants/constans-value"; import { ICON_SIZE_BUTTON, TEXT_SIZE_LARGE } from "@/constants/constans-value";
import AdminDonation_BoxOfDonationStory from "@/screens/Admin/Donation/BoxOfDonationStory"; import AdminDonation_BoxOfDonationStory from "@/screens/Admin/Donation/BoxOfDonationStory";
import { funUpdateStatusDonation } from "@/screens/Admin/Donation/funDonationUpdateStatus";
import { apiAdminDonationDetailById } from "@/service/api-admin/api-admin-donation";
import { colorBadgeStatus } from "@/utils/colorBadge";
import { formatCurrencyDisplay } from "@/utils/formatCurrencyDisplay";
import { Ionicons } from "@expo/vector-icons"; import { Ionicons } from "@expo/vector-icons";
import { router, useLocalSearchParams } from "expo-router"; import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
import _ from "lodash"; import _ from "lodash";
import React from "react"; import React from "react";
import { View } from "react-native"; import { View } from "react-native";
import Toast from "react-native-toast-message";
export default function AdminDonationDetail() { export default function AdminDonationDetail() {
const { id, status } = useLocalSearchParams(); const { id, status } = useLocalSearchParams();
const [openDrawer, setOpenDrawer] = React.useState(false); const [openDrawer, setOpenDrawer] = React.useState(false);
const colorBadge = () => { const [data, setData] = React.useState<any | null>(null);
if (status === "publish") { const [countDonatur, setCountDonatur] = React.useState(0);
return MainColor.green; const [isLoading, setIsLoading] = React.useState(false);
} else if (status === "review") {
return MainColor.orange; useFocusEffect(
} else if (status === "reject") { React.useCallback(() => {
return MainColor.red; onLoadData();
} else { }, [id])
return MainColor.placeholder; );
const onLoadData = async () => {
try {
const response = await apiAdminDonationDetailById({
id: id as string,
});
if (response.success) {
setData(response.data.donasi);
setCountDonatur(response.data.donatur);
}
} catch (error) {
console.log("[ERROR]", error);
setData(null);
} }
}; };
const listData = [ const listData = [
{ {
label: "Penggalang Dana", label: "Penggalang Dana",
value: `Bagas Banuna ${id}`, value: (data && data?.Author?.username) || "-",
}, },
{ {
label: "Judul", label: "Judul",
value: `Donasi Lorem ipsum dolor sit amet, consectetur adipisicing elit.`, value: (data && data?.title) || "-",
}, },
{ {
label: "Status", label: "Status",
value: ( value:
<BadgeCustom color={colorBadge()}> data && data?.DonasiMaster_Status?.name ? (
{_.startCase(status as string)} <BadgeCustom
</BadgeCustom> color={colorBadgeStatus({
), status: data?.DonasiMaster_Status?.name,
})}
>
{_.startCase(data?.DonasiMaster_Status?.name)}
</BadgeCustom>
) : (
"-"
),
}, },
{ {
label: "Durasi", label: "Durasi",
value: "30 Hari", value: (data && data?.DonasiMaster_Durasi?.name) + " hari" || "-",
}, },
{ {
label: "Target Dana", label: "Target Dana",
value: "Rp 10.000.000", value:
data && data?.target
? `Rp. ${formatCurrencyDisplay(data?.target)}`
: "-",
}, },
{ {
label: "Kategori", label: "Kategori",
value: "Kategori Donasi", value: (data && data?.DonasiMaster_Ketegori?.name) || "-",
}, },
// {
// label: "Total Donatur",
// value: "-",
// },
// {
// label: "Progress",
// value: "0 %",
// },
// {
// label: "Dana Terkumpul",
// value: "Rp 0",
// },
]; ];
const listPencarianDana = [ const listPencarianDana = [
{ {
label: "Total Dana Dicairkan", label: "Total Dana Dicairkan",
value: "Rp 0", value: `Rp ${(data && formatCurrencyDisplay(data?.totalPencairan)) || 0}`,
}, },
{ {
label: "Sisa Dana", label: "Sisa Dana Masuk",
value: "Rp 0", value: `Rp ${
(data &&
formatCurrencyDisplay(data?.terkumpul - data?.totalPencairan)) ||
0
}`,
}, },
{ {
label: "Akumulasi Pencairan", label: "Akumulasi Pencairan",
value: "0 kali", value: `${(data && data?.akumulasiPencairan) || 0} kali`,
}, },
{ {
label: "Bank Tujuan", label: "Bank Tujuan",
value: "BNI", value: (data && data?.namaBank) || "-",
}, },
{ {
label: "Nomor Rekening", label: "Nomor Rekening",
value: "123456789", value: (data && data?.rekening) || "-",
}, },
]; ];
const handleReport = async ({
changeStatus,
}: {
changeStatus: "publish" | "review" | "reject";
}) => {
try {
setIsLoading(true);
const response = await funUpdateStatusDonation({
id: id as string,
changeStatus,
data: data,
});
if (!response.success) {
Toast.show({
type: "error",
text1: "Update status gagal",
});
return;
}
Toast.show({
type: "success",
text1: "Update status berhasil",
});
router.back();
} catch (error) {
console.log("[ERROR]", error);
} finally {
setIsLoading(false);
}
};
const rightComponent = ( const rightComponent = (
<ActionIcon <ActionIcon
icon={<IconDot size={ICON_SIZE_BUTTON} />} icon={<IconDot size={ICON_SIZE_BUTTON} />}
@@ -118,8 +175,6 @@ export default function AdminDonationDetail() {
/> />
); );
return ( return (
<> <>
<ViewWrapper <ViewWrapper
@@ -140,18 +195,27 @@ export default function AdminDonationDetail() {
<StackCustom gap={5}> <StackCustom gap={5}>
{listPencarianDana.map((item, i) => ( {listPencarianDana.map((item, i) => (
<GridDetail_4_8 <GridSpan_4_8
key={i} key={i}
label={<TextCustom bold>{item.label}</TextCustom>} label={<TextCustom bold>{item.label}</TextCustom>}
value={<TextCustom>{item.value}</TextCustom>} value={<TextCustom>{item.value}</TextCustom>}
/> />
))} ))}
</StackCustom> </StackCustom>
<ButtonCustom <ButtonCustom
iconLeft={ iconLeft={
<Ionicons name="cash-outline" size={ICON_SIZE_BUTTON} /> <Ionicons name="cash-outline" size={ICON_SIZE_BUTTON} />
} }
disabled={data?.terkumpul - data?.totalPencairan <= 0}
onPress={() => { onPress={() => {
if (data?.terkumpul - data?.totalPencairan <= 0) {
Toast.show({
type: "error",
text1: "Tidak ada dana yang tersisa",
});
return;
}
router.push(`/admin/donation/${id}/disbursement-of-funds`); router.push(`/admin/donation/${id}/disbursement-of-funds`);
}} }}
> >
@@ -161,16 +225,32 @@ export default function AdminDonationDetail() {
</BaseBox> </BaseBox>
<BaseBox> <BaseBox>
<ProgressCustom size="lg" /> <ProgressCustom
size="lg"
value={Number(data?.progres) || 0}
showLabel={true}
label={data?.progres + "%"}
animated
color="primary"
/>
<Spacing /> <Spacing />
<StackCustom gap={"xs"}> <StackCustom gap={"xs"}>
<GridDetail_4_8 <GridSpan_4_8
label={<TextCustom bold>Jumlah Donatur</TextCustom>} label={<TextCustom bold>Jumlah Donatur</TextCustom>}
value={<TextCustom>0 orang</TextCustom>} value={
<TextCustom>
{countDonatur ? countDonatur : 0} orang
</TextCustom>
}
/> />
<GridDetail_4_8 <GridSpan_4_8
label={<TextCustom bold>Dana Terkumpul</TextCustom>} label={<TextCustom bold>Dana Terkumpul</TextCustom>}
value={<TextCustom>Rp 0</TextCustom>} value={
<TextCustom>
Rp {formatCurrencyDisplay(data?.terkumpul || 0)}
</TextCustom>
}
/> />
</StackCustom> </StackCustom>
</BaseBox> </BaseBox>
@@ -179,9 +259,9 @@ export default function AdminDonationDetail() {
<BaseBox> <BaseBox>
<StackCustom> <StackCustom>
<DummyLandscapeImage /> <DummyLandscapeImage imageId={data?.imageId || ""} />
{listData.map((item, i) => ( {listData.map((item, i) => (
<GridDetail_4_8 <GridSpan_4_8
key={i} key={i}
label={<TextCustom bold>{item.label}</TextCustom>} label={<TextCustom bold>{item.label}</TextCustom>}
value={<TextCustom>{item.value}</TextCustom>} value={<TextCustom>{item.value}</TextCustom>}
@@ -190,27 +270,33 @@ export default function AdminDonationDetail() {
</StackCustom> </StackCustom>
</BaseBox> </BaseBox>
<AdminDonation_BoxOfDonationStory data={data?.CeritaDonasi as any} />
{data &&
data?.catatan &&
(status === "review" || status === "reject") && (
<ReportBox text={data?.catatan} />
)}
{status === "review" && ( {status === "review" && (
<StackCustom> <StackCustom>
<AdminDonation_BoxOfDonationStory />
<AdminButtonReview <AdminButtonReview
isLoading={isLoading}
onPublish={() => { onPublish={() => {
AlertDefaultSystem({ AlertDefaultSystem({
title: "Publish", title: "Publish",
message: "Apakah anda yakin ingin mempublikasikan data ini?", message: "Apakah anda yakin ingin mempublikasikan data ini?",
textLeft: "Batal", textLeft: "Batal",
textRight: "Ya", textRight: "Ya",
onPressLeft: () => {
router.back();
},
onPressRight: () => { onPressRight: () => {
router.back(); handleReport({ changeStatus: "publish" });
}, },
}); });
}} }}
onReject={() => { onReject={() => {
router.push(`/admin/donation/${id}/reject-input`); router.push(
`/admin/donation/${id}/reject-input?status=${status}`
);
}} }}
/> />
</StackCustom> </StackCustom>
@@ -218,12 +304,12 @@ export default function AdminDonationDetail() {
{status === "reject" && ( {status === "reject" && (
<StackCustom> <StackCustom>
<AdminDonation_BoxOfDonationStory />
<AdminButtonReject <AdminButtonReject
title="Tambah Catatan" title="Tambah Catatan"
onReject={() => { onReject={() => {
router.push(`/admin/donation/${id}/reject-input`); router.push(
`/admin/donation/${id}/reject-input?status=${status}`
);
}} }}
/> />
</StackCustom> </StackCustom>

View File

@@ -8,52 +8,163 @@ import {
ViewWrapper, ViewWrapper,
} from "@/components"; } from "@/components";
import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButtonAntTitle"; import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButtonAntTitle";
import { GridDetail_4_8 } from "@/components/_ShareComponent/GridDetail_4_8"; import { GridSpan_4_8 } from "@/components/_ShareComponent/GridSpan_4_8";
import { MainColor } from "@/constants/color-palet"; import {
import dayjs from "dayjs"; apiAdminDonationInvoiceDetailById,
import { router, useLocalSearchParams } from "expo-router"; apiAdminDonationInvoiceUpdateById,
} from "@/service/api-admin/api-admin-donation";
import { colorBadgeTransaction } from "@/utils/colorBadge";
import { dateTimeView } from "@/utils/dateTimeView";
import { formatCurrencyDisplay } from "@/utils/formatCurrencyDisplay";
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 AdminDonasiTransactionDetail() { export default function AdminDonasiTransactionDetail() {
const { id } = useLocalSearchParams(); const { id, status } = useLocalSearchParams();
console.log("[STATUS]", id, status);
const buttonAction = ( const [data, setData] = useState<any | null>(null);
<BoxButtonOnFooter> const [isLoading, setLoading] = useState(false);
<ButtonCustom onPress={() => router.back()}>Terima</ButtonCustom>
</BoxButtonOnFooter> useFocusEffect(
useCallback(() => {
onLoadData();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [id])
); );
const onLoadData = async () => {
try {
const response = await apiAdminDonationInvoiceDetailById({
id: id as string,
});
console.log("[GET INVOICE BY ID]", JSON.stringify(response, null, 2));
if (response.success) {
setData(response.data);
}
} catch (error) {
console.log("[ERROR]", error);
}
};
const handlerSubmit = async () => {
try {
setLoading(true);
const newData = {
donationId: data?.donasiId,
nominal: data?.nominal,
};
const response = await apiAdminDonationInvoiceUpdateById({
id: id as string,
data: newData,
status: "berhasil",
});
console.log("[UPDATE INVOICE]", JSON.stringify(response, null, 2));
if (!response.success) {
Toast.show({
type: "error",
text1: response.message,
});
return;
}
Toast.show({
type: "success",
text1: response.message,
});
router.back();
} catch (error) {
console.log("[ERROR]", error);
} finally {
setLoading(false);
}
};
const buttonAction = () => {
if (data && data?.DonasiMaster_StatusInvoice?.name === "Menunggu") {
return null;
}
if (data && data?.DonasiMaster_StatusInvoice?.name === "Proses") {
return (
<BoxButtonOnFooter>
<ButtonCustom
isLoading={isLoading}
onPress={() => {
handlerSubmit();
}}
>
Terima donasi
</ButtonCustom>
</BoxButtonOnFooter>
);
}
return (
<BoxButtonOnFooter>
<ButtonCustom disabled>
{data?.DonasiMaster_StatusInvoice?.name}
</ButtonCustom>
</BoxButtonOnFooter>
);
};
const listData = [ const listData = [
{ {
label: "Donatur", label: "Donatur",
value: "Bagas Banuna", value: (data && data?.Author?.username) || "-",
}, },
{ {
label: "Bank", label: "Bank",
value: "BCA", value: (data && data?.MasterBank?.namaBank) || "-",
}, },
{ {
label: "Jumlah Donasi", label: "Jumlah Donasi",
value: "Rp. 1.000.000", value: `Rp. ${
(data && data?.nominal && formatCurrencyDisplay(data?.nominal)) || "-"
}`,
}, },
{ {
label: "Status", label: "Status",
value: <BadgeCustom color={MainColor.green}>Berhasil</BadgeCustom>, value:
(data && data?.DonasiMaster_StatusInvoice?.name && (
<BadgeCustom
color={colorBadgeTransaction({
status: data?.DonasiMaster_StatusInvoice?.name as any,
})}
>
{_.startCase(
(data?.DonasiMaster_StatusInvoice?.name as any) || "-"
)}
</BadgeCustom>
)) ||
"-",
}, },
{ {
label: "Tanggal", label: "Tanggal",
value: dayjs().format("DD-MM-YYYY HH:mm:ss"), value: (data && dateTimeView({ date: data?.createdAt })) || "-",
}, },
{ {
label: "Bukti Transfer", label: "Bukti Transfer",
value: ( value:
<ButtonCustom (data && data?.imageId && (
onPress={() => <ButtonCustom
router.push(`/(application)/(image)/preview-image/${id}`) onPress={() =>
} router.push(
> `/(application)/(image)/preview-image/${data?.imageId}`
Cek )
</ButtonCustom> }
), >
Cek
</ButtonCustom>
)) ||
"-",
}, },
]; ];
@@ -61,12 +172,12 @@ export default function AdminDonasiTransactionDetail() {
<> <>
<ViewWrapper <ViewWrapper
headerComponent={<AdminBackButtonAntTitle title="Detail Transaksi" />} headerComponent={<AdminBackButtonAntTitle title="Detail Transaksi" />}
footerComponent={buttonAction} footerComponent={buttonAction()}
> >
<BaseBox> <BaseBox>
<StackCustom> <StackCustom>
{listData.map((item, index) => ( {listData.map((item, index) => (
<GridDetail_4_8 <GridSpan_4_8
key={index} key={index}
label={<TextCustom bold>{item.label}</TextCustom>} label={<TextCustom bold>{item.label}</TextCustom>}
value={<TextCustom>{item.value}</TextCustom>} value={<TextCustom>{item.value}</TextCustom>}

View File

@@ -1,3 +1,4 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { import {
BaseBox, BaseBox,
ButtonCustom, ButtonCustom,
@@ -6,28 +7,54 @@ import {
ViewWrapper, ViewWrapper,
} from "@/components"; } from "@/components";
import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButtonAntTitle"; import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButtonAntTitle";
import { GridDetail_4_8 } from "@/components/_ShareComponent/GridDetail_4_8"; import { GridSpan_4_8 } from "@/components/_ShareComponent/GridSpan_4_8";
import dayjs from "dayjs"; import { apiAdminDonationDisbursementOfFundsListById } from "@/service/api-admin/api-admin-donation";
import { router, useLocalSearchParams } from "expo-router"; import { dateTimeView } from "@/utils/dateTimeView";
import { formatCurrencyDisplay } from "@/utils/formatCurrencyDisplay";
import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
import React, { useCallback } from "react";
export default function AdminDonationDetailDisbursementOfFunds() { export default function AdminDonationDetailDisbursementOfFunds() {
const { id } = useLocalSearchParams(); const { id } = useLocalSearchParams();
const [data, setData] = React.useState<any | null>(null);
useFocusEffect(
useCallback(() => {
onLoadData();
}, [id])
);
const onLoadData = async () => {
try {
const response = await apiAdminDonationDisbursementOfFundsListById({
id: id as string,
category: "get-one",
});
if (response.success) {
setData(response.data);
}
} catch (error) {
console.log("[ERROR]", error);
}
};
const listData = [ const listData = [
{ {
label: "Nominal", label: "Nominal",
value: "Rp 1.000.000", value: `Rp ${(data && formatCurrencyDisplay(data?.nominalCair)) || 0}`,
}, },
{ {
label: "Tanggal", label: "Tanggal",
value: dayjs().format("DD-MM-YYYY HH:mm"), value: dateTimeView({ date: data?.createdAt }),
}, },
{ {
label: "Judul", label: "Judul",
value: `Judul Pencairan Dana ${id}`, value: (data && data?.title) || "-",
}, },
{ {
label: "Deskripsi", label: "Deskripsi",
value: `Lorem ipsum dolor sit amet consectetur adipisicing elit. Itaque velit eos facere a dicta nemo repellendus harum laboriosam quos, earum reprehenderit. Nisi sapiente, quo earum quis alias ullam temporibus quidem.`, value: (data && data?.deskripsi) || "-",
}, },
]; ];
return ( return (
@@ -39,8 +66,8 @@ export default function AdminDonationDetailDisbursementOfFunds() {
> >
<BaseBox> <BaseBox>
<StackCustom> <StackCustom>
{listData.map((item, index) => ( {listData?.map((item, index) => (
<GridDetail_4_8 <GridSpan_4_8
key={index} key={index}
label={<TextCustom bold>{item.label}</TextCustom>} label={<TextCustom bold>{item.label}</TextCustom>}
value={<TextCustom>{item.value}</TextCustom>} value={<TextCustom>{item.value}</TextCustom>}
@@ -51,7 +78,7 @@ export default function AdminDonationDetailDisbursementOfFunds() {
<ButtonCustom <ButtonCustom
onPress={() => onPress={() =>
router.push(`/(application)/(image)/preview-image/${id}`) router.push(`/(application)/(image)/preview-image/${data?.imageId}`)
} }
> >
Cek Bukti Transaksi Cek Bukti Transaksi

View File

@@ -1,3 +1,4 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { import {
BaseBox, BaseBox,
BoxButtonOnFooter, BoxButtonOnFooter,
@@ -12,15 +13,122 @@ import {
ViewWrapper, ViewWrapper,
} from "@/components"; } from "@/components";
import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButtonAntTitle"; import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButtonAntTitle";
import { router, useLocalSearchParams } from "expo-router"; import DIRECTORY_ID from "@/constants/directory-id";
import { apiAdminDonationDetailById, apiAdminDonationDisbursementOfFundsCreated } from "@/service/api-admin/api-admin-donation";
import { uploadFileService } from "@/service/upload-service";
import { formatCurrencyDisplay } from "@/utils/formatCurrencyDisplay";
import pickFile from "@/utils/pickFile";
import { Image } from "expo-image";
import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
import React from "react";
import Toast from "react-native-toast-message";
export default function AdminDonationDisbursementOfFunds() { export default function AdminDonationDisbursementOfFunds() {
const { id } = useLocalSearchParams(); const { id } = useLocalSearchParams();
const handleSubmit = (
const [data, setData] = React.useState<any | null>(null);
const [isLoading, setIsLoading] = React.useState(false);
const [value, setValue] = React.useState({
nominalCair: "",
title: "",
deskripsi: "",
});
const [image, setImage] = React.useState<any | null>(null);
useFocusEffect(
React.useCallback(() => {
onLoadData();
}, [id])
);
const onLoadData = async () => {
try {
const response = await apiAdminDonationDetailById({
id: id as string,
});
if (response.success) {
setData(response.data.donasi);
}
} catch (error) {
console.log("[ERROR]", error);
setData(null);
}
};
const handleSubmit = async () => {
if (!image) {
Toast.show({
type: "error",
text1: "Harap upload bukti transfer",
});
return;
}
if (!value.nominalCair || !value.title || !value.deskripsi) {
Toast.show({
type: "error",
text1: "Harap isi semua data",
});
return;
}
try {
setIsLoading(true);
const uploadImage = await uploadFileService({
dirId: DIRECTORY_ID.donasi_bukti_trf_pencairan_dana,
imageUri: image.uri,
});
if (!uploadFileService) {
Toast.show({
type: "error",
text1: "Gagal mengunggah gambar",
});
return;
}
const imageId = uploadImage.data.id;
const newData = {
...value,
imageId: imageId,
};
const response = await apiAdminDonationDisbursementOfFundsCreated({
id: id as string,
data: newData,
});
if (!response.success) {
Toast.show({
type: "error",
text1: response.message,
});
return;
}
Toast.show({
type: "success",
text1: "Pencairan dana berhasil disimpan",
});
router.back();
} catch (error) {
console.log("[ERROR]", error);
} finally {
setIsLoading(false);
}
};
const buttonSubmit = (
<BoxButtonOnFooter> <BoxButtonOnFooter>
<ButtonCustom <ButtonCustom
isLoading={isLoading}
onPress={() => { onPress={() => {
router.back(); handleSubmit();
}} }}
> >
Simpan Simpan
@@ -31,7 +139,7 @@ export default function AdminDonationDisbursementOfFunds() {
return ( return (
<ViewWrapper <ViewWrapper
headerComponent={<AdminBackButtonAntTitle title="Pencairan Dana" />} headerComponent={<AdminBackButtonAntTitle title="Pencairan Dana" />}
footerComponent={handleSubmit} footerComponent={buttonSubmit}
> >
<BaseBox> <BaseBox>
<StackCustom gap="md"> <StackCustom gap="md">
@@ -39,7 +147,7 @@ export default function AdminDonationDisbursementOfFunds() {
Dana Tersisa Dana Tersisa
</TextCustom> </TextCustom>
<TextCustom align="center" bold size="large"> <TextCustom align="center" bold size="large">
Rp 1.000.000 Rp {formatCurrencyDisplay(data?.terkumpul - data?.totalPencairan)}
</TextCustom> </TextCustom>
</StackCustom> </StackCustom>
</BaseBox> </BaseBox>
@@ -56,9 +164,27 @@ export default function AdminDonationDisbursementOfFunds() {
label="Nominal" label="Nominal"
placeholder="0" placeholder="0"
iconLeft={"Rp"} iconLeft={"Rp"}
value={value.nominalCair}
onChangeText={(text) => {
setValue({
...value,
nominalCair: text,
});
}}
/> />
<TextInputCustom required label="Judul" placeholder="Masukan judul" /> <TextInputCustom
required
label="Judul"
placeholder="Masukan judul"
value={value.title}
onChangeText={(text) => {
setValue({
...value,
title: text,
});
}}
/>
<TextAreaCustom <TextAreaCustom
required required
@@ -66,20 +192,37 @@ export default function AdminDonationDisbursementOfFunds() {
placeholder="Masukan deskripsi" placeholder="Masukan deskripsi"
showCount showCount
maxLength={500} maxLength={500}
value={value.deskripsi}
onChangeText={(text) => {
setValue({
...value,
deskripsi: text,
});
}}
/> />
</StackCustom> </StackCustom>
</BaseBox> </BaseBox>
<InformationBox text="Wajib menyertakan bukti transfer" />
<Spacing />
<InformationBox text="Wajib menyertakan bukti transfer" />
<ButtonCenteredOnly <ButtonCenteredOnly
onPress={() => { onPress={() => {
router.push(`/(application)/(image)/take-picture/${id}`); pickFile({
allowedType: "image",
aspectRatio: [9, 16],
setImageUri: (file) => {
setImage(file);
},
});
}} }}
icon="upload" icon="upload"
> >
Upload Upload
</ButtonCenteredOnly> </ButtonCenteredOnly>
<Spacing /> <Spacing />
<Image source={image?.uri} style={{ width: "100%", height: 300 }} />
<Spacing />
</ViewWrapper> </ViewWrapper>
); );
} }

View File

@@ -1,21 +1,54 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { import {
ActionIcon, ActionIcon,
CenterCustom, CenterCustom,
Divider, Divider,
StackCustom, LoaderCustom,
TextCustom, StackCustom,
ViewWrapper TextCustom,
ViewWrapper,
} from "@/components"; } from "@/components";
import { IconView } from "@/components/_Icon/IconComponent"; import { IconView } from "@/components/_Icon/IconComponent";
import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButtonAntTitle"; import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButtonAntTitle";
import { GridViewCustomSpan } from "@/components/_ShareComponent/GridViewCustomSpan"; import { GridViewCustomSpan } from "@/components/_ShareComponent/GridViewCustomSpan";
import { ICON_SIZE_BUTTON } from "@/constants/constans-value"; import { ICON_SIZE_BUTTON } from "@/constants/constans-value";
import { apiAdminDonationDisbursementOfFundsListById } from "@/service/api-admin/api-admin-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, { useCallback } from "react";
import { View } from "react-native"; import { View } from "react-native";
export default function AdminDonasiListOfDisbursementOfFunds() { export default function AdminDonasiListOfDisbursementOfFunds() {
const { id } = useLocalSearchParams(); const { id } = useLocalSearchParams();
const [listData, setListData] = React.useState<any[] | null>(null);
const [loadData, setLoadData] = React.useState(false);
useFocusEffect(
useCallback(() => {
onLoadData();
}, [id])
);
const onLoadData = async () => {
try {
setLoadData(true);
const response = await apiAdminDonationDisbursementOfFundsListById({
id: id as string,
category: "get-all",
});
if (response.success) {
setListData(response.data);
}
} catch (error) {
console.log("[ERROR]", error);
} finally {
setLoadData(false);
}
};
return ( return (
<> <>
<ViewWrapper <ViewWrapper
@@ -45,36 +78,47 @@ export default function AdminDonasiListOfDisbursementOfFunds() {
/> />
<Divider /> <Divider />
<StackCustom> <StackCustom>
{Array.from({ length: 10 }).map((_, index) => ( {loadData ? (
<View key={index}> <LoaderCustom />
<GridViewCustomSpan ) : _.isEmpty(listData) ? (
span1={3} <TextCustom align="center" color="gray">
span2={5} Belum ada data
span3={4} </TextCustom>
component1={ ) : (
<CenterCustom> listData?.map((item, index) => (
<ActionIcon <View key={index}>
icon={<IconView size={ICON_SIZE_BUTTON} color="black" />} <GridViewCustomSpan
onPress={() => { span1={3}
router.push( span2={5}
`/admin/donation/${id}/detail-disbursement-of-funds` span3={4}
); component1={
}} <CenterCustom>
/> <ActionIcon
</CenterCustom> icon={
} <IconView size={ICON_SIZE_BUTTON} color="black" />
component2={ }
<TextCustom bold align="center" truncate> onPress={() => {
{dayjs() router.push(
.add(index + 1, "day") `/admin/donation/${item?.id}/detail-disbursement-of-funds`
.format("DD-MM-YYYY HH:mm")} );
</TextCustom> }}
} />
component3={<TextCustom>Rp. 1.000.000</TextCustom>} </CenterCustom>
/> }
<Divider /> component2={
</View> <TextCustom align="center" truncate>
))} {dayjs(item?.createdAt).format("DD-MM-YYYY")}
</TextCustom>
}
component3={
<TextCustom align="center" truncate>
Rp. {formatCurrencyDisplay(item?.nominalCair)}
</TextCustom>
}
/>
</View>
))
)}
</StackCustom> </StackCustom>
</ViewWrapper> </ViewWrapper>
</> </>

View File

@@ -1,7 +1,9 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { import {
ActionIcon, ActionIcon,
BadgeCustom, BadgeCustom,
CenterCustom, CenterCustom,
LoaderCustom,
SelectCustom, SelectCustom,
StackCustom, StackCustom,
TextCustom, TextCustom,
@@ -10,23 +12,91 @@ import {
import { IconView } from "@/components/_Icon/IconComponent"; import { IconView } from "@/components/_Icon/IconComponent";
import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButtonAntTitle"; import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButtonAntTitle";
import { GridViewCustomSpan } from "@/components/_ShareComponent/GridViewCustomSpan"; import { GridViewCustomSpan } from "@/components/_ShareComponent/GridViewCustomSpan";
import { MainColor } from "@/constants/color-palet";
import { ICON_SIZE_BUTTON } from "@/constants/constans-value"; import { ICON_SIZE_BUTTON } from "@/constants/constans-value";
import { dummyMasterStatusTransaction } from "@/lib/dummy-data/_master/status-transaction"; import { apiAdminDonationListOfDonatur } from "@/service/api-admin/api-admin-donation";
import { router, useLocalSearchParams } from "expo-router"; import { apiMasterTransaction } from "@/service/api-client/api-master";
import React from "react"; import { colorBadgeTransaction } from "@/utils/colorBadge";
import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
import _ from "lodash";
import React, { useEffect } from "react";
import { View } from "react-native"; import { View } from "react-native";
import { Divider } from "react-native-paper"; import { Divider } from "react-native-paper";
export default function AdminDonasiListOfDonatur() { export default function AdminDonasiListOfDonatur() {
const { id } = useLocalSearchParams(); const { id } = useLocalSearchParams();
const [listData, setListData] = React.useState<any[] | null>(null);
const [loadData, setLoadData] = React.useState(false);
const [master, setMaster] = React.useState<any[]>([]);
const [selectValue, setSelectValue] = React.useState<string | null>(null);
const [selectedStatus, setSelectedStatus] = React.useState<string | null>(
null
);
useFocusEffect(
React.useCallback(() => {
onLoadData();
}, [id, selectValue])
);
const onLoadData = async () => {
try {
setLoadData(true);
const response = await apiAdminDonationListOfDonatur({
id: id as string,
status: selectedStatus as any,
});
// console.log("[LIST OF DONATUR]", JSON.stringify(response, null, 2));
if (response.success) {
setListData(response.data);
}
} catch (error) {
console.log("[ERROR]", error);
setListData([]);
} finally {
setLoadData(false);
}
};
useEffect(() => {
onLoadMaster();
}, []);
const onLoadMaster = async () => {
try {
const response = await apiMasterTransaction();
if (response.success) {
setMaster(response.data);
}
} catch (error) {
console.log("[ERROR]", error);
setMaster([]);
}
};
const searchComponent = ( const searchComponent = (
<View style={{ flexDirection: "row", gap: 5 }}> <View style={{ flexDirection: "row", gap: 5 }}>
<SelectCustom <SelectCustom
placeholder="Pilih status transaksi" placeholder="Pilih status transaksi"
data={dummyMasterStatusTransaction} data={
onChange={(value) => console.log(value)} _.isEmpty(master)
? []
: master?.map((item: any) => ({
label: item.name,
value: item.id,
}))
}
value={selectValue}
onChange={(value: any) => {
setSelectValue(value);
const nameSelected = master.find((item: any) => item.id === value);
const statusChooses = _.lowerCase(nameSelected?.name);
setSelectedStatus(statusChooses);
}}
styleContainer={{ width: "100%", marginBottom: 0 }} styleContainer={{ width: "100%", marginBottom: 0 }}
allowClear
/> />
</View> </View>
); );
@@ -37,63 +107,78 @@ export default function AdminDonasiListOfDonatur() {
<AdminBackButtonAntTitle newComponent={searchComponent} /> <AdminBackButtonAntTitle newComponent={searchComponent} />
} }
> >
<GridViewCustomSpan
span1={3}
span2={5}
span3={4}
component1={
<TextCustom bold align="center">
Aksi
</TextCustom>
}
component2={
<TextCustom bold align="center">
Donatur
</TextCustom>
}
component3={
<TextCustom bold align="center">
Status
</TextCustom>
}
/>
<Divider />
<StackCustom> <StackCustom>
{Array.from({ length: 10 }).map((_, index) => ( <GridViewCustomSpan
<View key={index}> span1={3}
<GridViewCustomSpan span2={5}
span1={3} span3={4}
span2={5} component1={
span3={4} <TextCustom bold align="center">
component1={ Aksi
<CenterCustom> </TextCustom>
<ActionIcon }
icon={<IconView size={ICON_SIZE_BUTTON} color="black" />} component2={
onPress={() => { <TextCustom bold align="center">
router.push( Donatur
`/admin/donation/${id}/berhasil/transaction-detail` </TextCustom>
); }
}} component3={
/> <TextCustom bold align="center">
</CenterCustom> Status
} </TextCustom>
component2={ }
<TextCustom bold align="center" truncate> />
Bagas Banuna <Divider />
</TextCustom> <StackCustom>
} {loadData ? (
component3={ <LoaderCustom />
<BadgeCustom ) : _.isEmpty(listData) ? (
style={{ alignSelf: "center" }} <TextCustom align="center" color="gray">
color={MainColor.green} Belum ada data
> </TextCustom>
Berhasil ) : (
</BadgeCustom> listData?.map((item: any, index: number) => (
} <View key={index}>
/> <GridViewCustomSpan
<Divider /> span1={3}
</View> span2={5}
))} span3={4}
component1={
<CenterCustom>
<ActionIcon
icon={
<IconView size={ICON_SIZE_BUTTON} color="black" />
}
onPress={() => {
router.push(
`/admin/donation/${item?.id}/${_.lowerCase(
item?.DonasiMaster_StatusInvoice?.name
)}/transaction-detail`
);
}}
/>
</CenterCustom>
}
component2={
<TextCustom bold align="center" truncate>
{item?.Author?.username || "-"}
</TextCustom>
}
component3={
<BadgeCustom
style={{ alignSelf: "center" }}
color={colorBadgeTransaction({
status: item?.DonasiMaster_StatusInvoice?.name,
})}
>
{item?.DonasiMaster_StatusInvoice?.name}
</BadgeCustom>
}
/>
</View>
))
)}
</StackCustom>
</StackCustom> </StackCustom>
</ViewWrapper> </ViewWrapper>
</> </>

View File

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

View File

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

View File

@@ -1,17 +1,56 @@
import { import {
BoxButtonOnFooter, BoxButtonOnFooter,
ButtonCustom, ButtonCustom,
StackCustom,
TextCustom,
TextInputCustom, TextInputCustom,
ViewWrapper, ViewWrapper,
} from "@/components"; } from "@/components";
import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButtonAntTitle"; import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButtonAntTitle";
import { MainColor } from "@/constants/color-palet";
import { apiAdminMasterDonationCategoryCreate } from "@/service/api-admin/api-master-admin";
import { useRouter } from "expo-router"; import { useRouter } from "expo-router";
import { useState } from "react";
import { Switch } from "react-native-paper";
import Toast from "react-native-toast-message";
export default function AdminDonationCategoryCreate() { export default function AdminDonationCategoryCreate() {
const router = useRouter(); const router = useRouter();
const [loading, setLoading] = useState(false);
const [data, setData] = useState({
name: "",
active: false,
});
const onSubmit = async () => {
try {
setLoading(true);
const response = await apiAdminMasterDonationCategoryCreate({ data });
if (response.success) {
Toast.show({
type: "success",
text2: "Data berhasil disimpan",
});
router.back();
return;
}
Toast.show({
type: "error",
text1: "Gagal menyimpan data",
});
} catch (error) {
console.log("[Error]", error);
} finally {
setLoading(false);
}
};
const buttonSubmit = ( const buttonSubmit = (
<BoxButtonOnFooter> <BoxButtonOnFooter>
<ButtonCustom onPress={() => router.back()}>Simpan</ButtonCustom> <ButtonCustom isLoading={loading} onPress={onSubmit}>
Simpan
</ButtonCustom>
</BoxButtonOnFooter> </BoxButtonOnFooter>
); );
return ( return (
@@ -20,7 +59,23 @@ export default function AdminDonationCategoryCreate() {
headerComponent={<AdminBackButtonAntTitle title="Tambah Kategori" />} headerComponent={<AdminBackButtonAntTitle title="Tambah Kategori" />}
footerComponent={buttonSubmit} footerComponent={buttonSubmit}
> >
<TextInputCustom placeholder="Masukkan Kategori" /> <TextInputCustom
label=""
placeholder="Masukkan Kategori"
value={data.name}
onChangeText={(text) => setData({ ...data, name: text })}
/>
<StackCustom gap={"sm"}>
<TextCustom>Status</TextCustom>
<Switch
style={{
alignSelf: "flex-start",
}}
color={MainColor.yellow}
value={data.active}
onValueChange={(value) => setData({ ...data, active: value })}
/>
</StackCustom>
</ViewWrapper> </ViewWrapper>
</> </>
); );

View File

@@ -1,21 +1,76 @@
import { import {
AlertDefaultSystem,
BoxButtonOnFooter, BoxButtonOnFooter,
ButtonCustom, ButtonCustom,
StackCustom,
TextCustom,
TextInputCustom, TextInputCustom,
ViewWrapper, ViewWrapper,
} from "@/components"; } from "@/components";
import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButtonAntTitle"; import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButtonAntTitle";
import { MainColor } from "@/constants/color-palet";
import {
apiAdminMasterDonationCategoryById,
apiAdminMasterDonationCategoryUpdate,
} from "@/service/api-admin/api-master-admin";
import { useLocalSearchParams, useRouter } from "expo-router"; import { useLocalSearchParams, useRouter } from "expo-router";
import { useState } from "react"; import { useCallback, useEffect, useState } from "react";
import { Switch } from "react-native-paper";
export default function AdminDonationCategoryUpdate() { export default function AdminDonationCategoryUpdate() {
const router = useRouter();
const { id } = useLocalSearchParams(); const { id } = useLocalSearchParams();
const [value, setValue] = useState(id); const [value, setValue] = useState(id);
const router = useRouter(); const [data, setData] = useState<any>(null);
const [isLoading, setIsLoading] = useState(false);
useEffect(() => {
const fetchData = async () => {
const response = await apiAdminMasterDonationCategoryById({
id: id as any,
});
console.log(JSON.stringify(response.data, null, 2));
setData(response.data);
};
fetchData();
}, [id]);
const handlerSubmit = async () => {
try {
setIsLoading(true);
const response = await apiAdminMasterDonationCategoryUpdate({
id: id as any,
data: data,
});
console.log(JSON.stringify(response.data, null, 2));
router.back();
} catch (error) {
console.log(error);
} finally {
setIsLoading(false);
}
};
const buttonSubmit = ( const buttonSubmit = (
<BoxButtonOnFooter> <BoxButtonOnFooter>
<ButtonCustom onPress={() => router.back()}>Update</ButtonCustom> <ButtonCustom
disabled={isLoading || data?.name === ""}
isLoading={isLoading}
onPress={() => {
AlertDefaultSystem({
title: "Update Data",
message: "Apakah anda yakin ingin mengupdate data ini?",
textLeft: "Batal",
textRight: "Ya",
onPressLeft: () => {},
onPressRight: () => handlerSubmit(),
});
}}
>
Update
</ButtonCustom>
</BoxButtonOnFooter> </BoxButtonOnFooter>
); );
return ( return (
@@ -25,10 +80,28 @@ export default function AdminDonationCategoryUpdate() {
footerComponent={buttonSubmit} footerComponent={buttonSubmit}
> >
<TextInputCustom <TextInputCustom
label="Nama Kategori"
placeholder="Masukkan Kategori" placeholder="Masukkan Kategori"
value={value as any} value={data?.name}
onChangeText={setValue} onChangeText={(value) => setData({ ...data, name: value })}
/> />
<StackCustom
gap={"sm"}
style={{
alignContent: "flex-start",
}}
>
<TextCustom>Status</TextCustom>
<Switch
style={{
alignSelf: "flex-start",
}}
color={MainColor.yellow}
value={data?.active}
onValueChange={(value) => setData({ ...data, active: value })}
/>
</StackCustom>
</ViewWrapper> </ViewWrapper>
</> </>
); );

View File

@@ -1,27 +1,65 @@
import { import {
ActionIcon, BadgeCustom,
BaseBox, CenterCustom,
CenterCustom, ClickableCustom,
Spacing, Spacing,
StackCustom, StackCustom,
TextCustom, TextCustom,
ViewWrapper, ViewWrapper
} from "@/components"; } from "@/components";
import { IconEdit } from "@/components/_Icon";
import AdminActionIconPlus from "@/components/_ShareComponent/Admin/ActionIconPlus"; import AdminActionIconPlus from "@/components/_ShareComponent/Admin/ActionIconPlus";
import AdminComp_BoxTitle from "@/components/_ShareComponent/Admin/BoxTitlePage"; import AdminComp_BoxTitle from "@/components/_ShareComponent/Admin/BoxTitlePage";
import AdminTitlePage from "@/components/_ShareComponent/Admin/TitlePage"; import AdminTitlePage from "@/components/_ShareComponent/Admin/TitlePage";
import { GridView_3_3_6 } from "@/components/_ShareComponent/GridView_3_3_6"; import { GridSpan_4_8 } from "@/components/_ShareComponent/GridSpan_4_8";
import { MainColor } from "@/constants/color-palet"; import { apiAdminMasterDonationCategory } from "@/service/api-admin/api-master-admin";
import { ICON_SIZE_BUTTON } from "@/constants/constans-value"; import { colorActivationForBadge } from "@/utils/colorActivationForBadge";
import { View } from "react-native"; import { router, useFocusEffect } from "expo-router";
import { Divider, Switch } from "react-native-paper"; import { useCallback, useState } from "react";
import { router } from "expo-router"; import { RefreshControl, View } from "react-native";
import { Divider } from "react-native-paper";
export default function AdminDonationCategory() { export default function AdminDonationCategory() {
const [listData, setListData] = useState<any[]>([]);
const [refreshing, setRefreshing] = useState(false);
const [loading, setLoading] = useState(false);
useFocusEffect(
useCallback(() => {
fetchMaster();
}, [])
);
const fetchMaster = async () => {
try {
setLoading(true);
const response = await apiAdminMasterDonationCategory();
if (response.success) {
console.log(JSON.stringify(response.data, null, 2));
setListData(response.data);
} else {
setListData([]);
}
} catch (error) {
console.log("[Error]", error);
} finally {
setLoading(false);
}
};
const onRefresh = async () => {
setRefreshing(true);
await fetchMaster();
setRefreshing(false);
};
return ( return (
<> <>
<ViewWrapper headerComponent={<AdminTitlePage title="Donasi" />}> <ViewWrapper
refreshControl={
<RefreshControl refreshing={refreshing} onRefresh={onRefresh} />
}
headerComponent={<AdminTitlePage title="Donasi" />}
>
<AdminComp_BoxTitle <AdminComp_BoxTitle
title="Kategori" title="Kategori"
rightComponent={ rightComponent={
@@ -33,81 +71,65 @@ export default function AdminDonationCategory() {
} }
/> />
<BaseBox> <View>
<GridView_3_3_6 <GridSpan_4_8
component1={ label={<TextCustom bold>Status</TextCustom>}
<TextCustom bold align="center"> value={<TextCustom bold>Kategori</TextCustom>}
Aksi
</TextCustom>
}
component2={<TextCustom bold>Status</TextCustom>}
component3={<TextCustom bold>Kategori</TextCustom>}
/> />
{/* <Grid>
<Grid.Col style={{ paddingLeft: 10 }} span={4}>
<TextCustom bold>Status</TextCustom>
</Grid.Col>
<Grid.Col span={8}>
<TextCustom bold>Kategori</TextCustom>
</Grid.Col>
</Grid> */}
<Divider /> <Divider />
<Spacing /> <Spacing />
<StackCustom> <StackCustom>
{listData.map((item, index) => ( {listData.map((item, index) => (
<View key={index}> <ClickableCustom
<GridView_3_3_6 onPress={() => {
component1={ router.push(`/admin/donation/category-update?id=${item.id}`);
}}
key={index}
>
<GridSpan_4_8
label={
<CenterCustom> <CenterCustom>
<ActionIcon <BadgeCustom
icon={ color={colorActivationForBadge({
<IconEdit size={ICON_SIZE_BUTTON} color="black" /> status: item.active,
} })}
onPress={() => { >
router.push(`/admin/donation/category-update?id=${index}`); {item.active ? "Aktif" : "Tidak Aktif"}
}} </BadgeCustom>
/>
</CenterCustom> </CenterCustom>
} }
component2={ value={<TextCustom>{item.name}</TextCustom>}
<Switch
value={true}
onValueChange={(item) => {
console.log(item);
}}
color={MainColor.yellow}
/>
}
component3={<TextCustom bold>{item.label}</TextCustom>}
/> />
<Spacing height={10} /> {/* <Grid containerStyle={{ paddingBottom: 10 }}>
<Grid.Col span={4} style={{ paddingLeft: 10 }}>
<CenterCustom>
<BadgeCustom
color={item.active ? MainColor.green : MainColor.red}
>
{item.active ? "Aktif" : "Tidak Aktif"}
</BadgeCustom>
</CenterCustom>
</Grid.Col>
<Grid.Col span={8}>
<TextCustom bold>{item.name}</TextCustom>
</Grid.Col>
</Grid> */}
<Divider /> <Divider />
</View> </ClickableCustom>
))} ))}
</StackCustom> </StackCustom>
</BaseBox> </View>
</ViewWrapper> </ViewWrapper>
</> </>
); );
} }
const listData = [
{
label: "Kegiatan Sosial",
value: "kegiatan_sosial",
},
{
label: "Pendidikan",
value: "pendidikan",
},
{
label: "Kesehatan",
value: "kesehatan",
},
{
label: "Kebudayaan",
value: "kebudayaan",
},
{
label: "Bencana Alami",
value: "bencana_alami",
},
{
label: "Lainnya",
value: "lainnya",
},
];

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -9,13 +9,67 @@ import {
import AdminComp_BoxDashboard from "@/components/_ShareComponent/Admin/BoxDashboard"; import AdminComp_BoxDashboard from "@/components/_ShareComponent/Admin/BoxDashboard";
import AdminTitlePage from "@/components/_ShareComponent/Admin/TitlePage"; import AdminTitlePage from "@/components/_ShareComponent/Admin/TitlePage";
import { MainColor } from "@/constants/color-palet"; import { MainColor } from "@/constants/color-palet";
import { apiAdminEvent } from "@/service/api-admin/api-admin-event";
import { useFocusEffect } from "expo-router";
import { useCallback, useState } from "react";
export default function AdminVoting() { export default function AdminVoting() {
const [data, setData] = useState<any | null>(null);
useFocusEffect(
useCallback(() => {
onLoadData();
}, [])
);
const onLoadData = async () => {
try {
const response = await apiAdminEvent({
category: "dashboard",
});
if (response.success) {
setData(response.data);
}
} catch (error) {
console.log("[ERROR]", error);
}
};
const listData = [
{
label: "Publish",
value: (data && data.publish) || 0,
icon: <IconPublish size={25} color={MainColor.green} />,
},
{
label: "Review",
value: (data && data.review) || 0,
icon: <IconReview size={25} color={MainColor.orange} />,
},
{
label: "Reject",
value: (data && data.reject) || 0,
icon: <IconReject size={25} color={MainColor.red} />,
},
{
label: "Riwayat",
value: (data && data.history) || 0,
icon: <IconArchive size={25} color={MainColor.placeholder} />,
},
{
label: "Tipe Acara",
value: (data && data.typeOfEvent) || 0,
icon: <IconList size={25} color={MainColor.placeholder} />,
},
];
return ( return (
<> <>
<ViewWrapper> <ViewWrapper>
<AdminTitlePage title="Event" /> <AdminTitlePage title="Event" />
<Spacing /> <Spacing />
<StackCustom gap={"xs"}> <StackCustom gap={"xs"}>
{listData.map((item, i) => ( {listData.map((item, i) => (
<AdminComp_BoxDashboard key={i} item={item} /> <AdminComp_BoxDashboard key={i} item={item} />
@@ -25,31 +79,3 @@ export default function AdminVoting() {
</> </>
); );
} }
const listData = [
{
label: "Publish",
value: 3,
icon: <IconPublish size={25} color={MainColor.green} />,
},
{
label: "Review",
value: 8,
icon: <IconReview size={25} color={MainColor.orange} />,
},
{
label: "Reject",
value: 4,
icon: <IconReject size={25} color={MainColor.red} />,
},
{
label: "Riwayat",
value: 6,
icon: <IconArchive size={25} color={MainColor.placeholder} />,
},
{
label: "Tipe Acara",
value: 7,
icon: <IconList size={25} color={MainColor.placeholder} />,
},
];

View File

@@ -5,13 +5,48 @@ import {
ViewWrapper, ViewWrapper,
} from "@/components"; } from "@/components";
import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButtonAntTitle"; import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButtonAntTitle";
import { apiEventCreateTypeOfEvent } from "@/service/api-admin/api-master-admin";
import { useRouter } from "expo-router"; import { useRouter } from "expo-router";
import { useState } from "react";
import Toast from "react-native-toast-message";
export default function AdminEventTypeOfEventCreate() { export default function AdminEventTypeOfEventCreate() {
const router = useRouter(); const router = useRouter();
const [value, setValue] = useState("");
const [isLoading, setLoading] = useState<boolean>(false);
const handlerSubmit = async () => {
try {
setLoading(true);
const response = await apiEventCreateTypeOfEvent({
data: value,
});
if (!response.success) {
Toast.show({
type: "error",
text1: "Gagal menambahkan tipe acara",
});
return;
}
Toast.show({
type: "success",
text1: "Berhasil menambahkan tipe acara",
});
router.back();
} catch (error) {
console.log("[ERROR CREATE TYPE EVENT]", error);
} finally {
setLoading(false);
}
};
const buttonSubmit = ( const buttonSubmit = (
<BoxButtonOnFooter> <BoxButtonOnFooter>
<ButtonCustom onPress={() => router.back()}>Simpan</ButtonCustom> <ButtonCustom isLoading={isLoading} onPress={() => handlerSubmit()}>
Simpan
</ButtonCustom>
</BoxButtonOnFooter> </BoxButtonOnFooter>
); );
return ( return (
@@ -20,7 +55,11 @@ export default function AdminEventTypeOfEventCreate() {
headerComponent={<AdminBackButtonAntTitle title="Tambah Tipe Acara" />} headerComponent={<AdminBackButtonAntTitle title="Tambah Tipe Acara" />}
footerComponent={buttonSubmit} footerComponent={buttonSubmit}
> >
<TextInputCustom placeholder="Masukkan Tipe Acara" /> <TextInputCustom
placeholder="Masukkan Tipe Acara"
value={value}
onChangeText={setValue}
/>
</ViewWrapper> </ViewWrapper>
</> </>
); );

View File

@@ -1,23 +1,53 @@
import { import {
ActionIcon, ActionIcon,
BaseBox, BadgeCustom,
CenterCustom, CenterCustom,
LoaderCustom,
Spacing, Spacing,
StackCustom, StackCustom,
TextCustom, TextCustom,
ViewWrapper, ViewWrapper
} from "@/components"; } from "@/components";
import { IconEdit } from "@/components/_Icon"; import { IconEdit } from "@/components/_Icon";
import AdminActionIconPlus from "@/components/_ShareComponent/Admin/ActionIconPlus"; import AdminActionIconPlus from "@/components/_ShareComponent/Admin/ActionIconPlus";
import AdminComp_BoxTitle from "@/components/_ShareComponent/Admin/BoxTitlePage"; import AdminComp_BoxTitle from "@/components/_ShareComponent/Admin/BoxTitlePage";
import AdminTitlePage from "@/components/_ShareComponent/Admin/TitlePage"; import AdminTitlePage from "@/components/_ShareComponent/Admin/TitlePage";
import { GridDetail_4_8 } from "@/components/_ShareComponent/GridDetail_4_8"; import { GridViewCustomSpan } from "@/components/_ShareComponent/GridViewCustomSpan";
import { ICON_SIZE_BUTTON } from "@/constants/constans-value"; import { ICON_SIZE_BUTTON } from "@/constants/constans-value";
import { router } from "expo-router"; import { apiAdminMasterTypeOfEvent } from "@/service/api-admin/api-master-admin";
import { colorActivationForBadge } from "@/utils/colorActivationForBadge";
import { router, useFocusEffect } from "expo-router";
import _ from "lodash";
import { useCallback, useState } from "react";
import { View } from "react-native"; import { View } from "react-native";
import { Divider } from "react-native-paper"; import { Divider } from "react-native-paper";
export default function AdminEventTypeOfEvent() { export default function AdminEventTypeOfEvent() {
const [listData, setListData] = useState<any[] | null>(null);
const [loadData, setLoadData] = useState<boolean>(false);
useFocusEffect(
useCallback(() => {
onLoadData();
}, [])
);
const onLoadData = async () => {
try {
setLoadData(true);
const response = await apiAdminMasterTypeOfEvent();
if (response.success) {
setListData(response.data);
}
} catch (error) {
console.log("[ERROR]",error);
setListData([]);
} finally {
setLoadData(false);
}
};
return ( return (
<> <>
<ViewWrapper headerComponent={<AdminTitlePage title="Event" />}> <ViewWrapper headerComponent={<AdminTitlePage title="Event" />}>
@@ -32,73 +62,68 @@ export default function AdminEventTypeOfEvent() {
} }
/> />
<BaseBox> <>
<GridDetail_4_8 <GridViewCustomSpan
label={ span1={2}
span2={5}
span3={5}
component1={
<TextCustom bold align="center"> <TextCustom bold align="center">
Aksi Aksi
</TextCustom> </TextCustom>
} }
value={<TextCustom bold>Tipe Acara</TextCustom>} component2={<TextCustom bold align="center">Status</TextCustom>}
component3={<TextCustom bold>Tipe Acara</TextCustom>}
/> />
<Divider /> <Divider />
<Spacing /> <Spacing />
<StackCustom> <StackCustom>
{listData.map((item, index) => ( {loadData ? (
<View key={index}> <LoaderCustom />
<GridDetail_4_8 ) : _.isEmpty(listData) ? (
label={ <TextCustom align="center" color="gray">
<CenterCustom> Belum ada data
<ActionIcon </TextCustom>
icon={ ) : (
<IconEdit size={ICON_SIZE_BUTTON} color="black" /> listData?.map((item, index) => (
} <View key={index}>
onPress={() => { <GridViewCustomSpan
router.push(`/admin/event/type-update?id=${index}`); span1={2}
}} span2={5}
/> span3={5}
</CenterCustom> component1={
} <CenterCustom>
value={<TextCustom bold>{item.label}</TextCustom>} <ActionIcon
/> icon={
<Divider /> <IconEdit size={ICON_SIZE_BUTTON} color="black" />
</View> }
))} onPress={() => {
router.push(`/admin/event/type-update?id=${item.id}`);
}}
/>
</CenterCustom>
}
style2={{ alignItems: "center" }}
component2={
<CenterCustom>
<BadgeCustom
color={colorActivationForBadge({
status: item?.active,
})}
>
{item?.active ? "Aktif" : "Tidak Aktif"}
</BadgeCustom>
</CenterCustom>
}
component3={<TextCustom >{item.name}</TextCustom>}
/>
</View>
))
)}
</StackCustom> </StackCustom>
</BaseBox> </>
</ViewWrapper> </ViewWrapper>
</> </>
); );
} }
const listData = [
{
label: "Seminar",
value: "seminar",
},
{
label: "Workshop",
value: "workshop",
},
{
label: "Konferensi",
value: "konferensi",
},
{
label: "Lomba",
value: "lomba",
},
{
label: "Pameran",
value: "pameran",
},
{
label: "Pesta",
value: "pesta",
},
{
label: "Pertandingan",
value: "pertandingan",
},
];

View File

@@ -1,20 +1,91 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { import {
BoxButtonOnFooter, BoxButtonOnFooter,
ButtonCustom, ButtonCustom,
Spacing,
TextCustom,
TextInputCustom, TextInputCustom,
ViewWrapper, ViewWrapper,
} from "@/components"; } from "@/components";
import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButtonAntTitle"; import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButtonAntTitle";
import { useLocalSearchParams, useRouter } from "expo-router"; import { MainColor } from "@/constants/color-palet";
import {
apiAdminMasterTypeOfEventGetOne,
apiAdminMasterTypeOfEventUpdate,
} from "@/service/api-admin/api-master-admin";
import { useFocusEffect, useLocalSearchParams, useRouter } from "expo-router";
import { useCallback, useState } from "react";
import { Switch } from "react-native-paper";
import Toast from "react-native-toast-message";
export default function AdminEventTypeOfEventUpdate() { export default function AdminEventTypeOfEventUpdate() {
const { id } = useLocalSearchParams(); const { id } = useLocalSearchParams();
console.log("id >", id);
const router = useRouter(); const router = useRouter();
const [data, setData] = useState<{ name: string; active: boolean }>({
name: "",
active: false,
});
const [isLoading, setLoading] = useState<boolean>(false);
useFocusEffect(
useCallback(() => {
onLoadData();
}, [id])
);
const onLoadData = async () => {
try {
const response = await apiAdminMasterTypeOfEventGetOne({
id: id as string,
});
if (response.success) {
setData({
name: response.data.name,
active: response.data.active,
});
}
} catch (error) {
console.log("[ERROR UPDATE]", error);
}
};
const handlerSubmit = async () => {
try {
setLoading(true);
const response = await apiAdminMasterTypeOfEventUpdate({
id: id as string,
data: data,
});
if (!response.success) {
Toast.show({
type: "error",
text1: "Gagal mengupdate tipe acara",
});
return;
}
Toast.show({
type: "success",
text1: "Berhasil mengupdate tipe acara",
});
router.back();
} catch (error) {
console.log("[ERROR UPDATE]", error);
} finally {
setLoading(false);
}
};
const buttonSubmit = ( const buttonSubmit = (
<BoxButtonOnFooter> <BoxButtonOnFooter>
<ButtonCustom onPress={() => router.back()}>Update</ButtonCustom> <ButtonCustom isLoading={isLoading} onPress={() => handlerSubmit()}>
Update
</ButtonCustom>
</BoxButtonOnFooter> </BoxButtonOnFooter>
); );
return ( return (
@@ -23,7 +94,19 @@ export default function AdminEventTypeOfEventUpdate() {
headerComponent={<AdminBackButtonAntTitle title="Ubah Tipe Acara" />} headerComponent={<AdminBackButtonAntTitle title="Ubah Tipe Acara" />}
footerComponent={buttonSubmit} footerComponent={buttonSubmit}
> >
<TextInputCustom placeholder="Masukkan Tipe Acara" value="" /> <TextInputCustom
placeholder="Masukkan Tipe Acara"
value={data.name}
onChangeText={(text) => setData({ ...data, name: text })}
/>
<TextCustom>Aktivasi</TextCustom>
<Spacing height={10} />
<Switch
color={MainColor.yellow}
value={data.active}
onValueChange={(value) => setData({ ...data, active: value })}
/>
</ViewWrapper> </ViewWrapper>
</> </>
); );

View File

@@ -1,58 +1,79 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { import {
ActionIcon, ActionIcon,
AlertDefaultSystem,
BadgeCustom, BadgeCustom,
BaseBox, BaseBox,
DrawerCustom, DrawerCustom,
MenuDrawerDynamicGrid, MenuDrawerDynamicGrid,
Spacing,
StackCustom, StackCustom,
TextCustom, TextCustom,
ViewWrapper, ViewWrapper,
} from "@/components"; } from "@/components";
import { IconDot, IconView } from "@/components/_Icon/IconComponent"; import { IconDot } from "@/components/_Icon/IconComponent";
import { IconTrash } from "@/components/_Icon/IconTrash";
import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButtonAntTitle"; import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButtonAntTitle";
import AdminTitleTable from "@/components/_ShareComponent/Admin/TableTitle"; import { GridSpan_4_8 } from "@/components/_ShareComponent/GridSpan_4_8";
import AdminTableValue from "@/components/_ShareComponent/Admin/TableValue";
import { GridDetail_4_8 } from "@/components/_ShareComponent/GridDetail_4_8";
import { MainColor } from "@/constants/color-palet"; import { MainColor } from "@/constants/color-palet";
import { import { ICON_SIZE_XLARGE } from "@/constants/constans-value";
ICON_SIZE_BUTTON, import { apiAdminForumPostingById } from "@/service/api-admin/api-admin-forum";
ICON_SIZE_MEDIUM,
ICON_SIZE_XLARGE,
} from "@/constants/constans-value";
import { Ionicons } from "@expo/vector-icons"; import { Ionicons } from "@expo/vector-icons";
import { router } from "expo-router"; import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
import { useState } from "react"; import { useCallback, useState } from "react";
import { Divider } from "react-native-paper";
import Toast from "react-native-toast-message";
export default function AdminForumDetailPosting() { export default function AdminForumDetailPosting() {
const { id } = useLocalSearchParams();
const [openDrawerPage, setOpenDrawerPage] = useState(false); const [openDrawerPage, setOpenDrawerPage] = useState(false);
const [openDrawerAction, setOpenDrawerAction] = useState(false); const [data, setData] = useState<any | null>(null);
const [id, setId] = useState<any>(); useFocusEffect(
useCallback(() => {
onLoadData();
}, [id])
);
const handlerAction = (item: { value: string; path: string }) => { const onLoadData = async () => {
if (item.value === "delete") { try {
AlertDefaultSystem({ const response = await apiAdminForumPostingById({
title: "Hapus Posting", id: id as string,
message: "Apakah Anda yakin ingin menghapus posting ini?",
textLeft: "Batal",
textRight: "Hapus",
onPressRight: () => {
Toast.show({
type: "success",
text1: "Posting berhasil dihapus",
});
},
}); });
} else {
router.navigate(item.path as any); if (response.success) {
setData(response.data);
}
} catch (error) {
console.log("[ERROR]", error);
} }
setOpenDrawerAction(false);
}; };
const listDataAction = [
{
label: "Username",
value: data?.Author?.username || "-",
},
{
label: "Status",
value:
(data && (
<BadgeCustom
color={
data?.ForumMaster_StatusPosting?.status === "Open"
? MainColor.green
: MainColor.red
}
>
{data?.ForumMaster_StatusPosting?.status || "-"}
</BadgeCustom>
)) ||
"-",
},
{
label: "Komentar",
value: data?.JumlahKomentar || 0,
},
{
label: "Total Report",
value: data?.JumlahReportPosting || 0,
},
];
return ( return (
<> <>
<ViewWrapper <ViewWrapper
@@ -60,67 +81,42 @@ export default function AdminForumDetailPosting() {
<AdminBackButtonAntTitle <AdminBackButtonAntTitle
title="Detail Posting" title="Detail Posting"
rightComponent={ rightComponent={
<ActionIcon data &&
icon={<IconDot size={16} color={MainColor.darkblue} />} data?.isActive && (
onPress={() => setOpenDrawerPage(true)} <ActionIcon
/> icon={<IconDot size={16} color={MainColor.darkblue} />}
onPress={() => setOpenDrawerPage(true)}
/>
)
} }
/> />
} }
> >
{data && !data?.isActive && (
<BaseBox>
<TextCustom bold align="center" color="red">
Postingan ini telah di nonaktifkan
</TextCustom>
</BaseBox>
)}
<BaseBox> <BaseBox>
<StackCustom gap={"sm"}> <StackCustom gap={"sm"}>
{listDataAction.map((item, i) => ( {listDataAction.map((item, i) => (
<GridDetail_4_8 <GridSpan_4_8
key={i} key={i}
label={<TextCustom bold>{item.label}</TextCustom>} label={<TextCustom bold>{item.label}</TextCustom>}
value={<TextCustom>{item.value}</TextCustom>} value={<TextCustom>{item.value}</TextCustom>}
/> />
))} ))}
<TextCustom bold>Posting</TextCustom>
<TextCustom>
Lorem ipsum dolor sit amet consectetur adipisicing elit.
Asperiores cupiditate nobis dignissimos explicabo quo unde dolorum
numquam eos ab laborum fugiat illo nam velit quibusdam, maxime
assumenda aut vero provident!
</TextCustom>
</StackCustom> </StackCustom>
</BaseBox> </BaseBox>
{/* <AdminComp_BoxTitle title="Komentar" rightComponent={rightComponent} /> */}
<BaseBox> <BaseBox>
<AdminTitleTable title1="Aksi" title2="Username" title3="Komentar" /> <StackCustom gap={"sm"}>
<Spacing /> <TextCustom bold>Postingan</TextCustom>
<Divider /> <TextCustom>{(data && data?.diskusi) || "-"}</TextCustom>
{Array.from({ length: 10 }).map((_, index) => ( </StackCustom>
<AdminTableValue
key={index}
value1={
<ActionIcon
icon={
<Ionicons
name="ellipsis-vertical-outline"
size={ICON_SIZE_BUTTON}
color="black"
/>
}
onPress={() => {
setOpenDrawerAction(true);
setId(index + 1);
}}
/>
}
value2={<TextCustom truncate={1}>Username username</TextCustom>}
value3={
<TextCustom truncate={2}>
Lorem ipsum dolor sit amet consectetur adipisicing elit.
Blanditiis asperiores quidem deleniti architecto eaque et
nostrum, ad consequuntur eveniet quisquam quae voluptatum
ducimus! Dolorem nobis modi officia debitis, beatae mollitia.
</TextCustom>
}
/>
))}
</BaseBox> </BaseBox>
</ViewWrapper> </ViewWrapper>
@@ -143,6 +139,18 @@ export default function AdminForumDetailPosting() {
value: "detail", value: "detail",
path: `/admin/forum/${id}/list-report-posting`, path: `/admin/forum/${id}/list-report-posting`,
}, },
{
icon: (
<Ionicons
name="list"
size={ICON_SIZE_XLARGE}
color={MainColor.white}
/>
),
label: "Daftar Komentar",
value: "detail",
path: `/admin/forum/${id}/list-comment`,
},
]} ]}
onPressItem={(item) => { onPressItem={(item) => {
router.navigate(item.path as any); router.navigate(item.path as any);
@@ -150,54 +158,6 @@ export default function AdminForumDetailPosting() {
}} }}
/> />
</DrawerCustom> </DrawerCustom>
<DrawerCustom
isVisible={openDrawerAction}
closeDrawer={() => setOpenDrawerAction(false)}
height={"auto"}
>
<MenuDrawerDynamicGrid
data={[
{
icon: <IconView />,
label: "Detail Komentar",
value: "detail",
path: `admin/forum/${id}/list-report-comment`,
},
{
icon: (
<IconTrash size={ICON_SIZE_MEDIUM} color={MainColor.white} />
),
label: "Hapus Komentar",
value: "delete",
path: "",
color: MainColor.red,
},
]}
onPressItem={(item) => {
handlerAction(item as any);
}}
/>
</DrawerCustom>
</> </>
); );
} }
const listDataAction = [
{
label: "Username",
value: "Username",
},
{
label: "Status",
value: <BadgeCustom color={MainColor.green}>Open</BadgeCustom>,
},
{
label: "Komentar",
value: "10",
},
{
label: "Total Report",
value: "1",
},
];

View File

@@ -0,0 +1,91 @@
/* eslint-disable react-hooks/exhaustive-deps */
import {
LoaderCustom,
StackCustom,
TextCustom,
ViewWrapper
} from "@/components";
import { IconOpenTo } from "@/components/_Icon/IconOpenTo";
import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButtonAntTitle";
import AdminTitleTable from "@/components/_ShareComponent/Admin/TableTitle";
import AdminTableValue from "@/components/_ShareComponent/Admin/TableValue";
import { apiAdminForumCommentById } from "@/service/api-admin/api-admin-forum";
import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
import _ from "lodash";
import { useCallback, useState } from "react";
import { Divider } from "react-native-paper";
export default function AdminForumListComment() {
const { id } = useLocalSearchParams();
const [listComment, setListComment] = useState<any[] | null>(null);
const [loadList, setLoadList] = useState(false);
useFocusEffect(
useCallback(() => {
onLoadComment();
}, [id])
);
const onLoadComment = async () => {
try {
setLoadList(true);
const response = await apiAdminForumCommentById({
id: id as string,
category: "get-all",
});
if (response.success) {
setListComment(response.data);
}
} catch (error) {
console.log("[ERROR]", error);
setListComment([]);
} finally {
setLoadList(false);
}
};
return (
<>
<ViewWrapper
headerComponent={<AdminBackButtonAntTitle title="Daftar Komentar" />}
>
<StackCustom>
<AdminTitleTable title1="Aksi" title2="Report" title3="Komentar" />
<Divider />
{loadList ? (
<LoaderCustom />
) : _.isEmpty(listComment) ? (
<TextCustom align="center" color="gray">
Tidak ada komentar
</TextCustom>
) : (
listComment?.map((item: any, index: number) => (
<AdminTableValue
key={index}
value1={
<IconOpenTo
onPress={() => {
router.push(
`/admin/forum/${item.id}/list-report-comment`
);
}}
/>
}
value2={
<TextCustom truncate={1}>
{item?.countReport || 0}
</TextCustom>
}
value3={
<TextCustom truncate={2}>{item?.komentar || "-"}</TextCustom>
}
/>
))
)}
</StackCustom>
</ViewWrapper>
</>
);
}

View File

@@ -1,10 +1,11 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { import {
ActionIcon, ActionIcon,
AlertDefaultSystem, AlertDefaultSystem,
BaseBox, BaseBox,
DrawerCustom, DrawerCustom,
LoaderCustom,
MenuDrawerDynamicGrid, MenuDrawerDynamicGrid,
Spacing,
StackCustom, StackCustom,
TextCustom, TextCustom,
ViewWrapper, ViewWrapper,
@@ -15,17 +16,67 @@ import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButt
import AdminComp_BoxTitle from "@/components/_ShareComponent/Admin/BoxTitlePage"; import AdminComp_BoxTitle from "@/components/_ShareComponent/Admin/BoxTitlePage";
import AdminTitleTable from "@/components/_ShareComponent/Admin/TableTitle"; import AdminTitleTable from "@/components/_ShareComponent/Admin/TableTitle";
import AdminTableValue from "@/components/_ShareComponent/Admin/TableValue"; import AdminTableValue from "@/components/_ShareComponent/Admin/TableValue";
import { GridDetail_4_8 } from "@/components/_ShareComponent/GridDetail_4_8"; import { GridSpan_4_8 } from "@/components/_ShareComponent/GridSpan_4_8";
import { MainColor } from "@/constants/color-palet"; import { MainColor } from "@/constants/color-palet";
import { ICON_SIZE_BUTTON } from "@/constants/constans-value"; import { ICON_SIZE_BUTTON } from "@/constants/constans-value";
import { router } from "expo-router"; import {
import { useState } from "react"; apiAdminForumCommentById,
apiAdminForumDeactivateComment,
apiAdminForumListReportCommentById,
} from "@/service/api-admin/api-admin-forum";
import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
import _ from "lodash";
import { useCallback, useState } from "react";
import { Divider } from "react-native-paper"; import { Divider } from "react-native-paper";
import Toast from "react-native-toast-message"; import Toast from "react-native-toast-message";
export default function AdminForumReportComment() { export default function AdminForumReportComment() {
const { id } = useLocalSearchParams();
const [data, setData] = useState<any | null>(null);
const [listReport, setListReport] = useState<any[] | null>(null);
const [loadList, setLoadList] = useState(false);
const [openDrawer, setOpenDrawer] = useState(false); const [openDrawer, setOpenDrawer] = useState(false);
const [openDrawerAction, setOpenDrawerAction] = useState(false); const [openDrawerAction, setOpenDrawerAction] = useState(false);
const [selectedReport, setSelectedReport] = useState({
id: "",
username: "",
kategori: "",
keterangan: "",
deskripsi: "",
});
useFocusEffect(
useCallback(() => {
onLoadData();
}, [id])
);
const onLoadData = async () => {
try {
setLoadList(true);
const response = await apiAdminForumCommentById({
id: id as string,
category: "get-one",
});
const responseReport = await apiAdminForumListReportCommentById({
id: id as string,
});
if (response.success) {
setData(response.data);
}
if (responseReport.success) {
setListReport(responseReport.data);
}
} catch (error) {
console.log("[ERROR]", error);
setData(null);
setListReport([]);
} finally {
setLoadList(false);
}
};
return ( return (
<> <>
@@ -44,53 +95,65 @@ export default function AdminForumReportComment() {
> >
<BaseBox> <BaseBox>
<StackCustom gap={"sm"}> <StackCustom gap={"sm"}>
{listData.map((item, i) => ( <GridSpan_4_8
<GridDetail_4_8 label={<TextCustom bold>Username</TextCustom>}
key={i} value={<TextCustom>{data?.Author?.username || "-"}</TextCustom>}
label={<TextCustom bold>{item.label}</TextCustom>} />
value={<TextCustom>{item.value}</TextCustom>} <GridSpan_4_8
/> label={<TextCustom bold>Komentar</TextCustom>}
))} value={<TextCustom>{data?.komentar || "-"}</TextCustom>}
<TextCustom bold>Posting</TextCustom> />
<TextCustom>
Lorem ipsum dolor sit amet consectetur adipisicing elit.
Asperiores cupiditate nobis dignissimos explicabo quo unde dolorum
numquam eos ab laborum fugiat illo nam velit quibusdam, maxime
assumenda aut vero provident!
</TextCustom>
</StackCustom> </StackCustom>
</BaseBox> </BaseBox>
<AdminComp_BoxTitle title="Daftar Report Komentar" /> <AdminComp_BoxTitle title="Daftar Report Komentar" />
<BaseBox> <StackCustom>
<AdminTitleTable <AdminTitleTable
title1="Aksi" title1="Aksi"
title2="Username" title2="Pelapor"
title3="Kategori Report" title3="Kategori Report"
/> />
<Spacing />
<Divider /> <Divider />
{Array.from({ length: 5 }).map((_, index) => ( {loadList ? (
<AdminTableValue <LoaderCustom />
key={index} ) : _.isEmpty(listReport) ? (
value1={ <TextCustom align="center" color="gray">
<ActionIcon Tidak ada report
icon={<IconView size={ICON_SIZE_BUTTON} color="black" />} </TextCustom>
onPress={() => { ) : (
setOpenDrawerAction(true); listReport?.map((item: any, index: number) => (
}} <AdminTableValue
/> key={index}
} value1={
value2={<TextCustom truncate={1}>Username username</TextCustom>} <ActionIcon
value3={ icon={<IconView size={ICON_SIZE_BUTTON} color="black" />}
<TextCustom truncate={2} align="center"> onPress={() => {
SPAM setOpenDrawerAction(true);
</TextCustom> setSelectedReport({
} id: item.id,
/> username: item.User?.username,
))} kategori: item.ForumMaster_KategoriReport?.title,
</BaseBox> keterangan: item.ForumMaster_KategoriReport?.deskripsi,
deskripsi: item.deskripsi,
});
}}
/>
}
value2={
<TextCustom truncate={1}>
{item?.User?.username || "-"}
</TextCustom>
}
value3={
<TextCustom truncate={2} align="center">
{item?.ForumMaster_KategoriReport?.title || "-"}
</TextCustom>
}
/>
))
)}
</StackCustom>
</ViewWrapper> </ViewWrapper>
<DrawerCustom <DrawerCustom
@@ -114,7 +177,19 @@ export default function AdminForumReportComment() {
message: "Apakah Anda yakin ingin menghapus komentar ini?", message: "Apakah Anda yakin ingin menghapus komentar ini?",
textLeft: "Batal", textLeft: "Batal",
textRight: "Hapus", textRight: "Hapus",
onPressRight: () => { onPressRight: async () => {
const deleteComment = await apiAdminForumDeactivateComment({
id: id as string,
});
if (!deleteComment.success) {
Toast.show({
type: "error",
text1: "Komentar gagal dihapus",
});
return;
}
setOpenDrawer(false); setOpenDrawer(false);
Toast.show({ Toast.show({
type: "success", type: "success",
@@ -132,37 +207,39 @@ export default function AdminForumReportComment() {
closeDrawer={() => setOpenDrawerAction(false)} closeDrawer={() => setOpenDrawerAction(false)}
height={"auto"} height={"auto"}
> >
{listDataAction.map((item, i) => ( <StackCustom>
<GridDetail_4_8 <GridSpan_4_8
key={i} label={<TextCustom bold>Pelapor</TextCustom>}
label={<TextCustom bold>{item.label}</TextCustom>} value={<TextCustom>{selectedReport?.username || "-"}</TextCustom>}
value={<TextCustom>{item.value}</TextCustom>}
/> />
))}
{selectedReport?.kategori && (
<>
<GridSpan_4_8
label={<TextCustom bold>Kategori Report</TextCustom>}
value={
<TextCustom>{selectedReport?.kategori || "-"}</TextCustom>
}
/>
<GridSpan_4_8
label={<TextCustom bold>Keterangan</TextCustom>}
value={
<TextCustom>{selectedReport?.keterangan || "-"}</TextCustom>
}
/>
</>
)}
{selectedReport?.deskripsi && (
<GridSpan_4_8
label={<TextCustom bold>Deskripsi</TextCustom>}
value={
<TextCustom>{selectedReport?.deskripsi || "-"}</TextCustom>
}
/>
)}
</StackCustom>
</DrawerCustom> </DrawerCustom>
</> </>
); );
} }
const listData = [
{
label: "Username",
value: "Username",
},
];
const listDataAction = [
{
label: "Username",
value: "Riyusa",
},
{
label: "Kategori Report",
value: "SPAM",
},
{
label: "Deskripsi",
value:
"Lorem ipsum dolor sit amet consectetur adipisicing elit. Blanditiis asperiores quidem deleniti architecto eaque et nostrum, ad consequuntur eveniet quisquam quae voluptatum ducimus! Dolorem nobis modi officia debitis, beatae mollitia.",
},
];

View File

@@ -1,11 +1,12 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { import {
ActionIcon, ActionIcon,
AlertDefaultSystem, AlertDefaultSystem,
BadgeCustom, BadgeCustom,
BaseBox, BaseBox,
DrawerCustom, DrawerCustom,
LoaderCustom,
MenuDrawerDynamicGrid, MenuDrawerDynamicGrid,
Spacing,
StackCustom, StackCustom,
TextCustom, TextCustom,
ViewWrapper, ViewWrapper,
@@ -16,18 +17,67 @@ import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButt
import AdminComp_BoxTitle from "@/components/_ShareComponent/Admin/BoxTitlePage"; import AdminComp_BoxTitle from "@/components/_ShareComponent/Admin/BoxTitlePage";
import AdminTitleTable from "@/components/_ShareComponent/Admin/TableTitle"; import AdminTitleTable from "@/components/_ShareComponent/Admin/TableTitle";
import AdminTableValue from "@/components/_ShareComponent/Admin/TableValue"; import AdminTableValue from "@/components/_ShareComponent/Admin/TableValue";
import { GridDetail_4_8 } from "@/components/_ShareComponent/GridDetail_4_8"; import { GridSpan_4_8 } from "@/components/_ShareComponent/GridSpan_4_8";
import { MainColor } from "@/constants/color-palet"; import { MainColor } from "@/constants/color-palet";
import { ICON_SIZE_BUTTON } from "@/constants/constans-value"; import { ICON_SIZE_BUTTON } from "@/constants/constans-value";
import { router } from "expo-router"; import {
import { useState } from "react"; apiAdminForumDeactivatePosting,
apiAdminForumListReportPostingById,
apiAdminForumPostingById,
} from "@/service/api-admin/api-admin-forum";
import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
import _ from "lodash";
import { useCallback, useState } from "react";
import { Divider } from "react-native-paper"; import { Divider } from "react-native-paper";
import Toast from "react-native-toast-message"; import Toast from "react-native-toast-message";
export default function AdminForumReportPosting() { export default function AdminForumReportPosting() {
const { id } = useLocalSearchParams();
const [openDrawerPage, setOpenDrawerPage] = useState(false); const [openDrawerPage, setOpenDrawerPage] = useState(false);
const [openDrawerAction, setOpenDrawerAction] = useState(false); const [openDrawerAction, setOpenDrawerAction] = useState(false);
const [data, setData] = useState<any | null>(null);
const [listReport, setListReport] = useState<any[] | null>(null);
const [loadListReport, setLoadListReport] = useState(false);
const [selectedReport, setSelectedReport] = useState({
id: "",
username: "",
kategori: "",
keterangan: "",
deskripsi: "",
});
useFocusEffect(
useCallback(() => {
onLoadData();
}, [id])
);
const onLoadData = async () => {
try {
setLoadListReport(true);
const response = await apiAdminForumPostingById({
id: id as string,
});
const responseReport = await apiAdminForumListReportPostingById({
id: id as string,
});
if (response.success) {
setData(response.data);
}
if (responseReport.success) {
setListReport(responseReport.data);
}
} catch (error) {
console.log("[ERROR]", error);
} finally {
setLoadListReport(false);
}
};
return ( return (
<> <>
<ViewWrapper <ViewWrapper
@@ -45,50 +95,86 @@ export default function AdminForumReportPosting() {
> >
<BaseBox> <BaseBox>
<StackCustom gap={"sm"}> <StackCustom gap={"sm"}>
{listData.map((item, i) => ( <GridSpan_4_8
<GridDetail_4_8 label={<TextCustom bold>Username</TextCustom>}
key={i} value={<TextCustom>{data?.Author?.username || "-"}</TextCustom>}
label={<TextCustom bold>{item.label}</TextCustom>} />
value={<TextCustom>{item.value}</TextCustom>}
/> <GridSpan_4_8
))} label={<TextCustom bold>Status</TextCustom>}
<TextCustom bold>Posting</TextCustom> value={
<TextCustom> data && data?.ForumMaster_StatusPosting?.status ? (
Lorem ipsum dolor sit amet consectetur adipisicing elit. <BadgeCustom
Asperiores cupiditate nobis dignissimos explicabo quo unde dolorum color={
numquam eos ab laborum fugiat illo nam velit quibusdam, maxime data?.ForumMaster_StatusPosting?.status === "Open"
assumenda aut vero provident! ? MainColor.green
</TextCustom> : MainColor.red
}
>
{data?.ForumMaster_StatusPosting?.status === "Open"
? "Open"
: "Close"}
</BadgeCustom>
) : (
<TextCustom>{"-"}</TextCustom>
)
}
/>
<GridSpan_4_8
label={<TextCustom bold>Postingan</TextCustom>}
value={<TextCustom>{data?.diskusi || "-"}</TextCustom>}
/>
</StackCustom> </StackCustom>
</BaseBox> </BaseBox>
<AdminComp_BoxTitle title="Daftar Report Posting" /> <AdminComp_BoxTitle title="Daftar Report Posting" />
<BaseBox> <StackCustom gap={"sm"}>
<AdminTitleTable <AdminTitleTable
title1="Aksi" title1="Aksi"
title2="Username" title2="Pelapor"
title3="Kategori Report" title3="Kategori Report"
/> />
<Spacing />
<Divider /> <Divider />
{Array.from({ length: 5 }).map((_, index) => ( {loadListReport ? (
<AdminTableValue <LoaderCustom />
key={index} ) : _.isEmpty(listReport) ? (
value1={ <TextCustom align="center" color={"gray"}>
<ActionIcon Belum ada report
icon={<IconView size={ICON_SIZE_BUTTON} color="black" />} </TextCustom>
onPress={() => setOpenDrawerAction(true)} ) : (
/> listReport?.map((item: any, index: number) => (
} <AdminTableValue
value2={<TextCustom truncate={1}>Username username</TextCustom>} key={index}
value3={ value1={
<TextCustom truncate={2} align="center"> <ActionIcon
SPAM icon={<IconView size={ICON_SIZE_BUTTON} color="black" />}
</TextCustom> onPress={() => {
} setOpenDrawerAction(true);
/> setSelectedReport({
))} id: item?.id,
</BaseBox> username: item?.User?.username,
kategori: item?.ForumMaster_KategoriReport?.title,
keterangan: item?.ForumMaster_KategoriReport?.deskripsi,
deskripsi: item?.deskripsi,
});
}}
/>
}
value2={
<TextCustom truncate={1}>
{item?.User?.username || "-"}
</TextCustom>
}
value3={
<TextCustom truncate={2} align="center">
{item?.ForumMaster_KategoriReport?.title || "-"}
</TextCustom>
}
/>
))
)}
</StackCustom>
</ViewWrapper> </ViewWrapper>
<DrawerCustom <DrawerCustom
@@ -112,13 +198,25 @@ export default function AdminForumReportPosting() {
message: "Apakah Anda yakin ingin menghapus posting ini?", message: "Apakah Anda yakin ingin menghapus posting ini?",
textLeft: "Batal", textLeft: "Batal",
textRight: "Hapus", textRight: "Hapus",
onPressRight: () => { onPressRight: async () => {
const response = await apiAdminForumDeactivatePosting({
id: id as string,
});
if (!response.success) {
Toast.show({
type: "error",
text1: "Posting gagal dihapus",
});
return;
}
setOpenDrawerPage(false); setOpenDrawerPage(false);
Toast.show({ Toast.show({
type: "success", type: "success",
text1: "Posting berhasil dihapus", text1: "Posting berhasil dihapus",
}); });
router.back() router.back();
}, },
}); });
}} }}
@@ -130,41 +228,39 @@ export default function AdminForumReportPosting() {
closeDrawer={() => setOpenDrawerAction(false)} closeDrawer={() => setOpenDrawerAction(false)}
height={"auto"} height={"auto"}
> >
{listDataAction.map((item, i) => ( <StackCustom>
<GridDetail_4_8 <GridSpan_4_8
key={i} label={<TextCustom bold>Pelapor</TextCustom>}
label={<TextCustom bold>{item.label}</TextCustom>} value={<TextCustom>{selectedReport?.username || "-"}</TextCustom>}
value={<TextCustom>{item.value}</TextCustom>}
/> />
))}
{selectedReport?.kategori && (
<>
<GridSpan_4_8
label={<TextCustom bold>Kategori Report</TextCustom>}
value={
<TextCustom>{selectedReport?.kategori || "-"}</TextCustom>
}
/>
<GridSpan_4_8
label={<TextCustom bold>Keterangan</TextCustom>}
value={
<TextCustom>{selectedReport?.keterangan || "-"}</TextCustom>
}
/>
</>
)}
{selectedReport?.deskripsi && (
<GridSpan_4_8
label={<TextCustom bold>Deskripsi</TextCustom>}
value={
<TextCustom>{selectedReport?.deskripsi || "-"}</TextCustom>
}
/>
)}
</StackCustom>
</DrawerCustom> </DrawerCustom>
</> </>
); );
} }
const listData = [
{
label: "Username",
value: "Username",
},
{
label: "Status",
value: <BadgeCustom color={MainColor.green}>Open</BadgeCustom>,
},
];
const listDataAction = [
{
label: "Username",
value: "Firman Nusantara",
},
{
label: "Kategori Report",
value: "SPAM",
},
{
label: "Deskripsi",
value:
"Lorem ipsum dolor sit amet consectetur adipisicing elit. Blanditiis asperiores quidem deleniti architecto eaque et nostrum, ad consequuntur eveniet quisquam quae voluptatum ducimus! Dolorem nobis modi officia debitis, beatae mollitia.",
},
];

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