Compare commits

..

84 Commits

Author SHA1 Message Date
ca33dd83bb Perbaikan untuk apple store dan push notifikasi ke preview
Fix:
- app.config.js
- app/(application)/(user)/_layout.tsx
- app/(application)/admin/notification/index.tsx
- eas.json
- ios/HIPMIBadungConnect.xcodeproj/project.pbxproj
- ios/HIPMIBadungConnect/Info.plist

### No Issue
2026-01-12 17:40:15 +08:00
ea3fbdc541 Penambahan metode login dengan menerapkan EULA di awal
Add:
app/eula.tsx
screens/Authentication/EULAView.tsx

Fix:
- context/AuthContext.tsx
- screens/Authentication/RegisterView.tsx
- screens/RootLayout/AppRoot.tsx
- service/api-config.ts

### No Issue
2026-01-09 17:44:13 +08:00
33cd47aaed Fix mobile notification:
> - Bug penerima pesan 2 kali

Fix:
modified:   hooks/use-foreground-notifications.ts

### No Issue
2026-01-09 14:46:21 +08:00
57ac1eb45e Notifikasi admin to user
Fix:
- android/app/src/main/AndroidManifest.xml
- app/(application)/(user)/job/(tabs)/_layout.tsx
- app/(application)/(user)/job/[id]/index.tsx
- app/(application)/(user)/notifications/index.tsx
- app/(application)/(user)/profile/[id]/index.tsx
- app/(application)/admin/job/[id]/[status]/index.tsx
- app/(application)/admin/notification/index.tsx
- app/+not-found.tsx
- ios/HIPMIBadungConnect.xcodeproj/project.pbxproj
- screens/Admin/Job/funUpdateStatus.ts
- screens/Home/bottomFeatureSection.tsx

### No Issue
2026-01-08 17:48:53 +08:00
145ad73616 Fix job notifikasi
###  No Issue
2026-01-08 10:12:53 +08:00
7c85e35c61 Note:
- Fitur notifikasi ke admin dari user baru
- Notifikasi ke user bahwa akunnya telah terverifikasi

Fix:
- app/(application)/(user)/notifications/index.tsx
- app/(application)/(user)/waiting-room.tsx
- app/(application)/admin/super-admin/[id]/index.tsx
- app/(application)/admin/user-access/[id]/index.tsx
- context/AuthContext.tsx
- screens/Home/tabsList.ts
- service/api-admin/api-admin-user-access.ts
- service/api-device-token.ts
- service/api-notifications.ts
- types/type-notification-category.ts

Add:
- lib/routeApp.ts

### No Issue
2026-01-06 12:27:30 +08:00
d098b8ca16 Setup notifikasi untuk android
Fix:
- modified:   service/api-notifications.ts
- modified:   types/type-notification-category.ts

### No Issue
2026-01-05 12:27:23 +08:00
73a473cdc7 Tambah fitur notifikasi untuk android
Add:
- android/app/src/main/res/xml/

Fix:
- android/app/src/main/AndroidManifest.xml
- android/build.gradle
- app/(application)/(user)/test-notifications.tsx
- bun.lock
- package.json

### No Issue
2026-01-02 17:43:23 +08:00
3f85f330d2 Background notifikasi berhasil dibuat
Add:
-components/Notification/BackgroundNotificationHandler.tsx

### No Issue
2025-12-24 17:43:53 +08:00
7743a2467c Fitur notifikasi dan foreground
Add:
- types/type-notification-category.ts

Fix:
- app/(application)/(user)/notifications/index.tsx
- app/(application)/(user)/test-notifications.tsx
- app/(application)/admin/notification/index.tsx
- components/Notification/NotificationInitializer.tsx
- hooks/use-notification-store.tsx
- service/api-notifications.ts
- utils/formatChatTime.ts

### No Issue
2025-12-24 15:29:58 +08:00
54611ef812 Fix notifikasi system
Add:
- screens/Admin/AdminNotificationBell.tsx

Fix:
- app.config.js
- app/(application)/(user)/home.tsx
- app/(application)/(user)/test-notifications.tsx
- app/(application)/admin/_layout.tsx
- app/_layout.tsx
- components/Notification/NotificationInitializer.tsx
- context/AuthContext.tsx
- hooks/use-notification-store.tsx
- ios/HIPMIBadungConnect/Info.plist
- screens/Home/HeaderBell.tsx
- service/api-device-token.ts
- service/api-notifications.ts

### No Issue
2025-12-23 17:45:30 +08:00
1503707eed Notifikasi terhubung ke DB
Add:
- components/Notification/
- hooks/use-notification-store.tsx.back

Fix:
- app.config.js
- app/(application)/(user)/_layout.tsx
- app/(application)/(user)/home.tsx
- app/(application)/(user)/test-notifications.tsx
- app/(application)/_layout.tsx
- app/_layout.tsx
- components/_Icon/IconComponent.tsx
- components/_Icon/IconPlus.tsx
- components/_ShareComponent/NotificationInitializer.tsx
- context/AuthContext.tsx
- hooks/use-notification-store.tsx
- ios/HIPMIBadungConnect/Info.plist
- screens/Home/HeaderBell.tsx
- service/api-device-token.ts
- service/api-notifications.ts

### No Issue
2025-12-19 17:54:49 +08:00
a01a9bd93f Filter console dan clean code
Add:
- service/api-device-token.ts

Fix:
- app/(application)/(user)/home.tsx
- app/(application)/(user)/test-notifications.tsx
- app/_layout.tsx
- components/_ShareComponent/NotificationInitializer.tsx
- context/AuthContext.tsx
- hooks/use-foreground-notifications.ts
- screens/Home/HeaderBell.tsx
- service/api-notifications.ts

### No Issue
2025-12-17 17:46:28 +08:00
05c1cac10f Penerapan ke database
Fix:
- android/app/src/main/AndroidManifest.xml
- app/(application)/(user)/home.tsx
- components/_ShareComponent/NotificationInitializer.tsx
- ios/HIPMIBadungConnect.xcodeproj/project.pbxproj
- ios/HIPMIBadungConnect/Info.plist
- service/api-notifications.ts

### No Issue
2025-12-16 17:47:50 +08:00
d27c01ed56 Percobaan notifikasi
Add:
- app/(application)/(user)/test-notifications.tsx
- components/_ShareComponent/NotificationInitializer.tsx
- screens/Home/HeaderBell.tsx
- service/api-notifications.ts

Fix:
- app/(application)/(user)/home.tsx
- app/_layout.tsx
- screens/Authentication/LoginView.tsx

### No Issue
2025-12-15 17:46:05 +08:00
34680a4c38 test 2025-12-13 17:31:48 +08:00
43c8c105cf Notification firts commit
### No Issue
2025-12-12 17:50:49 +08:00
2c0198b1b7 Checkpoint QC Done
Cooming soon: Notification feature

### No Issue
2025-12-12 14:55:30 +08:00
573b525352 Fix QC ( Keano )
Fix:
- modified:   app/(application)/(user)/home.tsx
- modified:   app/(application)/(user)/voting/create.tsx
- modified:   components/DateInput/DataTimeAndroid.tsx
- modified:   components/DateInput/DateTimePickerCustom.tsx
- modified:   screens/Event/BoxPublishSection.tsx

### No Issue
2025-12-11 14:01:05 +08:00
6f9481c7c9 Fix QC ( Ayu )
Fix:
- modified:   app/(application)/(user)/event/[id]/publish.tsx
- modified:   app/(application)/(user)/event/create.tsx
- modified:   app/(application)/(user)/portofolio/[id]/create.tsx
- modified:   app/(application)/(user)/portofolio/[id]/edit.tsx
- modified:   app/(application)/admin/collaboration/[id]/group.tsx
- modified:   app/(application)/admin/collaboration/group.tsx
- modified:   app/(application)/admin/collaboration/publish.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/forum/posting.tsx
- modified:   app/(application)/admin/forum/report-comment.tsx
- modified:   app/(application)/admin/forum/report-posting.tsx
- modified:   app/(application)/admin/voting/[status]/status.tsx
- modified:   app/(application)/admin/voting/history.tsx
- modified:   components/Select/SelectCustom.tsx
- modified:   components/_ShareComponent/GridSpan_4_8.tsx
- modified:   screens/Authentication/LoginView.tsx
- modified:   screens/Collaboration/BoxPublishSection.tsx
- modified:   screens/Event/BoxDetailPublishSection.tsx
- modified:   screens/Home/topFeatureSection.tsx
- modified:   screens/Portofolio/ButtonCreatePortofolio.tsx

Add:
- app/(application)/admin/app-information/business-field/[id]/bidang-update.tsx
- app/(application)/admin/app-information/business-field/[id]/sub-bidang-update.tsx

### No Issue
2025-12-10 17:35:15 +08:00
cccb44a835 Fix QC Ayu
Fix:
- modified:   app/(application)/(user)/event/[id]/publish.tsx
- modified:   app/(application)/(user)/event/create.tsx
- modified:   app/(application)/(user)/portofolio/[id]/create.tsx
- modified:   app/(application)/(user)/portofolio/[id]/edit.tsx
- modified:   app/(application)/admin/collaboration/[id]/group.tsx
- modified:   app/(application)/admin/collaboration/group.tsx
- modified:   app/(application)/admin/collaboration/publish.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/forum/posting.tsx
- modified:   app/(application)/admin/forum/report-comment.tsx
- modified:   app/(application)/admin/forum/report-posting.tsx
- modified:   app/(application)/admin/voting/[status]/status.tsx
- modified:   app/(application)/admin/voting/history.tsx
- modified:   components/Select/SelectCustom.tsx
- modified:   components/_ShareComponent/GridSpan_4_8.tsx
- modified:   screens/Authentication/LoginView.tsx
- modified:   screens/Collaboration/BoxPublishSection.tsx
- modified:   screens/Event/BoxDetailPublishSection.tsx
- modified:   screens/Home/topFeatureSection.tsx
- modified:   screens/Portofolio/ButtonCreatePortofolio.tsx

Add:
- components/_ShareComponent/GridSpan_NewComponent.tsx

### No Issue
2025-12-09 17:36:36 +08:00
0f5862ce70 Fix Apple Reject:
Add:
- app/(application)/(user)/forum/terms.tsx

Fix:
- app/(application)/(user)/_layout.tsx
- app/(application)/(user)/home.tsx
- screens/Home/tabsList.ts
- service/api-client/api-user.ts

### No Issue
2025-12-08 16:34:33 +08:00
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
0770237fe5 Integrasi API: Admin Job
Add:
-  service/api-admin/api-admin-job.ts

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

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

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

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

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

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

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

- Perbaikan berupa integrasi API

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

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

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

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

### No issue
2025-10-13 17:46:47 +08:00
338 changed files with 18206 additions and 5181 deletions

42
.gitignore vendored
View File

@@ -40,3 +40,45 @@ app-example
.qodo
.env
# @generated expo-cli sync-8d4afeec25ea8a192358fae2f8e2fc766bdce4ec
# The following patterns were generated by expo-cli
# Learn more https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files
# dependencies
node_modules/
# Expo
.expo/
dist/
web-build/
expo-env.d.ts
# Native
*.orig.*
*.jks
*.p8
*.p12
*.key
*.mobileprovision
# Metro
.metro-health-check*
# debug
npm-debug.*
yarn-debug.*
yarn-error.*
# macOS
.DS_Store
*.pem
# local env files
.env*.local
# typescript
*.tsbuildinfo
# @end expo-cli

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)
@@ -82,6 +82,14 @@ def enableProguardInReleaseBuilds = (findProperty('android.enableProguardInRelea
def jscFlavor = 'io.github.react-native-community:jsc-android:2026004.+'
android {
// @generated begin @rnmapbox/maps-libcpp - expo prebuild (DO NOT MODIFY) sync-e24830a5a3e854b398227dfe9630aabfaa1cadd1
packagingOptions {
pickFirst 'lib/x86/libc++_shared.so'
pickFirst 'lib/x86_64/libc++_shared.so'
pickFirst 'lib/arm64-v8a/libc++_shared.so'
pickFirst 'lib/armeabi-v7a/libc++_shared.so'
}
// @generated end @rnmapbox/maps-libcpp
ndkVersion rootProject.ext.ndkVersion
buildToolsVersion rootProject.ext.buildToolsVersion
@@ -92,8 +100,10 @@ android {
applicationId 'com.bip.hipmimobileapp'
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 1
versionName "1.0.0"
versionCode 3
versionName "1.0.1"
buildConfigField "String", "REACT_NATIVE_RELEASE_LEVEL", "\"${findProperty('reactNativeReleaseLevel') ?: 'stable'}\""
}
signingConfigs {
debug {
@@ -111,15 +121,18 @@ android {
// Caution! In production, you need to generate your own keystore file.
// see https://reactnative.dev/docs/signed-apk-android.
signingConfig signingConfigs.debug
shrinkResources (findProperty('android.enableShrinkResourcesInReleaseBuilds')?.toBoolean() ?: false)
minifyEnabled enableProguardInReleaseBuilds
def enableShrinkResources = findProperty('android.enableShrinkResourcesInReleaseBuilds') ?: 'false'
shrinkResources enableShrinkResources.toBoolean()
minifyEnabled enableMinifyInReleaseBuilds
proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
crunchPngs (findProperty('android.enablePngCrunchInReleaseBuilds')?.toBoolean() ?: true)
def enablePngCrunchInRelease = findProperty('android.enablePngCrunchInReleaseBuilds') ?: 'true'
crunchPngs enablePngCrunchInRelease.toBoolean()
}
}
packagingOptions {
jniLibs {
useLegacyPackaging (findProperty('expo.useLegacyPackaging')?.toBoolean() ?: false)
def enableLegacyPackaging = findProperty('expo.useLegacyPackaging') ?: 'false'
useLegacyPackaging enableLegacyPackaging.toBoolean()
}
}
androidResources {
@@ -175,3 +188,5 @@ dependencies {
implementation jscFlavor
}
}
apply plugin: 'com.google.gms.google-services'

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">
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
@@ -13,7 +15,11 @@
<data android:scheme="https"/>
</intent>
</queries>
<application android:name=".MainApplication" android:label="@string/app_name" android:icon="@mipmap/ic_launcher" android:roundIcon="@mipmap/ic_launcher_round" android:allowBackup="true" android:theme="@style/AppTheme" android:supportsRtl="true">
<application android:name=".MainApplication" android:label="@string/app_name" android:icon="@mipmap/ic_launcher" android:roundIcon="@mipmap/ic_launcher_round" android:allowBackup="true" android:theme="@style/AppTheme" android:supportsRtl="true" android:enableOnBackInvokedCallback="false" android:fullBackupContent="@xml/secure_store_backup_rules" android:dataExtractionRules="@xml/secure_store_data_extraction_rules">
<meta-data android:name="com.google.firebase.messaging.default_notification_color" android:resource="@color/notification_icon_color" tools:replace="android:resource"/>
<meta-data android:name="com.google.firebase.messaging.default_notification_icon" android:resource="@drawable/notification_icon"/>
<meta-data android:name="expo.modules.notifications.default_notification_color" android:resource="@color/notification_icon_color"/>
<meta-data android:name="expo.modules.notifications.default_notification_icon" android:resource="@drawable/notification_icon"/>
<meta-data android:name="expo.modules.updates.ENABLED" android:value="false"/>
<meta-data android:name="expo.modules.updates.EXPO_UPDATES_CHECK_ON_LAUNCH" android:value="ALWAYS"/>
<meta-data android:name="expo.modules.updates.EXPO_UPDATES_LAUNCH_WAIT_MS" android:value="0"/>
@@ -29,6 +35,12 @@
<data android:scheme="hipmimobile"/>
<data android:scheme="exp+hipmi-mobile"/>
</intent-filter>
<intent-filter android:autoVerify="true" data-generated="true">
<action android:name="android.intent.action.VIEW"/>
<data android:scheme="https" android:host="cld-dkr-staging-hipmi.wibudev.com" android:pathPrefix="/"/>
<category android:name="android.intent.category.BROWSABLE"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity>
</application>
</manifest>

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 97 KiB

After

Width:  |  Height:  |  Size: 154 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 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

@@ -3,4 +3,5 @@
<color name="iconBackground">#ffffff</color>
<color name="colorPrimary">#023c69</color>
<color name="colorPrimaryDark">#ffffff</color>
<color name="notification_icon_color">#ffffff</color>
</resources>

View File

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

View File

@@ -1,5 +1,6 @@
<resources xmlns:tools="http://schemas.android.com/tools">
<style name="AppTheme" parent="Theme.EdgeToEdge">
<style name="AppTheme" parent="Theme.AppCompat.DayNight.NoActionBar">
<item name="android:enforceNavigationBarContrast" tools:targetApi="29">true</item>
<item name="android:editTextBackground">@drawable/rn_edit_text_material</item>
<item name="colorPrimary">@color/colorPrimary</item>
<item name="android:statusBarColor">#ffffff</item>
@@ -8,5 +9,6 @@
<item name="windowSplashScreenBackground">@color/splashscreen_background</item>
<item name="windowSplashScreenAnimatedIcon">@drawable/splashscreen_logo</item>
<item name="postSplashScreenTheme">@style/AppTheme</item>
<item name="android:windowSplashScreenBehavior">icon_preferred</item>
</style>
</resources>

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<full-backup-content>
<exclude domain="sharedpref" path="SECURESTORE"/>
</full-backup-content>

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<data-extraction-rules>
<cloud-backup>
<exclude domain="sharedpref" path="SECURESTORE"/>
</cloud-backup>
<device-transfer>
<exclude domain="sharedpref" path="SECURESTORE"/>
</device-transfer>
</data-extraction-rules>

View File

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

View File

@@ -15,7 +15,7 @@ org.gradle.jvmargs=-Xmx2048m -XX:MaxMetaspaceSize=512m
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
org.gradle.parallel=true
# AndroidX package structure to make it clearer which packages are bundled with the
# Android operating system, and which are packaged with your app's APK
@@ -41,6 +41,11 @@ newArchEnabled=true
# If set to false, you will be using JSC instead.
hermesEnabled=true
# Use this property to enable edge-to-edge display support.
# This allows your app to draw behind system bars for an immersive UI.
# Note: Only works with ReactActivity and should not be used with custom Activity.
edgeToEdgeEnabled=true
# Enable GIF support in React Native images (~200 B increase)
expo.gif.enabled=true
# Enable webp support in React Native images (~85 KB increase)
@@ -55,5 +60,6 @@ EX_DEV_CLIENT_NETWORK_INSPECTOR=true
# Use legacy packaging to compress native libraries in the resulting APK.
expo.useLegacyPackaging=false
# Whether the app is configured to use edge-to-edge via the app config or `react-native-edge-to-edge` plugin
expo.edgeToEdgeEnabled=true
# Specifies whether the app is configured to use edge-to-edge via the app config or plugin
# WARNING: This property has been deprecated and will be removed in Expo SDK 55. Use `edgeToEdgeEnabled` or `react.edgeToEdgeEnabled` to determine whether the project is using edge-to-edge.
expo.edgeToEdgeEnabled=true

Binary file not shown.

View File

@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME

4
android/gradlew vendored
View File

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

4
android/gradlew.bat vendored
View File

@@ -70,11 +70,11 @@ goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
set CLASSPATH=
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
:end
@rem End local scope for the variables with windows NT shell

View File

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

View File

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

View File

@@ -1,4 +1,6 @@
import { BackButton } from "@/components";
import { IconPlus } from "@/components/_Icon";
import { IconDot } from "@/components/_Icon/IconComponent";
import LeftButtonCustom from "@/components/Button/BackButton";
import { MainColor } from "@/constants/color-palet";
import { ICON_SIZE_SMALL } from "@/constants/constans-value";
@@ -10,6 +12,13 @@ export default function UserLayout() {
return (
<>
<Stack screenOptions={HeaderStyles}>
<Stack.Screen
name="delete-account"
options={{
title: "Hapus Akun",
headerLeft: () => <BackButton />,
}}
/>
<Stack.Screen
name="waiting-room"
options={{
@@ -49,6 +58,12 @@ export default function UserLayout() {
options={{
title: "Notifikasi",
headerLeft: () => <BackButton />,
// headerRight: () => (
// <IconPlus
// color={MainColor.yellow}
// onPress={() => router.push("/test-notifications")}
// />
// ),
}}
/>
@@ -504,7 +519,8 @@ export default function UserLayout() {
name="job/(tabs)"
options={{
title: "Job Vacancy",
headerLeft: () => <BackButton path="/home" />,
// headerLeft: () => <BackButton path="/home" />,
// Note: headerLeft di pindahkan ke Tabs Layout
}}
/>
<Stack.Screen
@@ -588,6 +604,13 @@ export default function UserLayout() {
headerLeft: () => <BackButton />,
}}
/>
<Stack.Screen
name="forum/terms"
options={{
title: "Syarat & Ketentuan Forum",
headerLeft: () => <BackButton />,
}}
/>
{/* ========== Maps Section ========= */}
<Stack.Screen

View File

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

View File

@@ -155,7 +155,7 @@ export default function CollaborationCreate() {
<TextAreaCustom
required
label="Keuntungan Proyek"
placeholder="Masukan keuntungan proyek"
placeholder="Masukan keuntungan proyek, contoh: Meningkatkan relasi bisnis , menjamin kualitas produk, meningkatkan kinerja dan lain lain"
showCount
maxLength={1000}
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 {
BaseBox,
Grid,
@@ -7,11 +8,60 @@ import {
ViewWrapper,
} from "@/components";
import { MainColor } from "@/constants/color-palet";
import { apiDonationGetInvoiceById } from "@/service/api-client/api-donation";
import { GStyles } from "@/styles/global-styles";
import { dateTimeView } from "@/utils/dateTimeView";
import { formatCurrencyDisplay } from "@/utils/formatCurrencyDisplay";
import { FontAwesome6 } from "@expo/vector-icons";
import dayjs from "dayjs";
import { useFocusEffect, useLocalSearchParams } from "expo-router";
import { useCallback, useState } from "react";
export default function DonasiFailed() {
const { id, invoiceId } = useLocalSearchParams();
const [data, setData] = useState<any>(null);
useFocusEffect(
useCallback(() => {
onLoadData();
}, [id, invoiceId])
);
const onLoadData = async () => {
try {
const response = await apiDonationGetInvoiceById({
id: invoiceId as string,
});
console.log("[DATA]", JSON.stringify(response.data, null, 2));
setData(response.data);
} catch (error) {
console.log("[ERROR]", error);
}
};
const listData = [
{
label: "Bank",
value: (data && data?.MasterBank?.namaBank) || "-",
},
{
label: "Rekening Penerima",
value: (data && data?.MasterBank?.namaAkun) || "-",
},
{
label: "No Rekening",
value: (data && data?.MasterBank?.norek) || "-",
},
{
label: "Jumlah Donasi",
value: (data && formatCurrencyDisplay(data?.nominal)) || "-",
},
{
label: "Tanggal",
value: (data && dateTimeView({ date: data?.createdAt })) || "-",
},
];
return (
<ViewWrapper>
<StackCustom>
@@ -58,26 +108,3 @@ export default function DonasiFailed() {
</ViewWrapper>
);
}
const listData = [
{
label: "Bank",
value: " BCA",
},
{
label: "Rekening Penerima",
value: "Himpunan Pengusaha Muda Indonesia",
},
{
label: "No Rekening",
value: "2304235678854332",
},
{
label: "Jumlah Donasi",
value: "Rp. 750.000",
},
{
label: "Tanggal",
value: `${dayjs(new Date()).format("DD/MM/YYYY")}`,
},
];

View File

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

View File

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

View File

@@ -1,17 +1,71 @@
/* eslint-disable react-hooks/exhaustive-deps */
import {
BaseBox,
ButtonCenteredOnly,
Grid,
InformationBox,
LoaderCustom,
StackCustom,
TextCustom,
ViewWrapper,
} from "@/components";
import {
apiDonationDisbursementOfFundsListById,
apiDonationGetOne,
} from "@/service/api-client/api-donation";
import { formatCurrencyDisplay } from "@/utils/formatCurrencyDisplay";
import dayjs from "dayjs";
import { router, useLocalSearchParams } from "expo-router";
import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
import _ from "lodash";
import React, { useState } from "react";
export default function DonationFundDisbursement() {
const { id } = useLocalSearchParams();
const [data, setData] = useState({
totalPencairan: 0,
akumulasiPencairan: 0,
});
const [listData, setListData] = React.useState<any[] | null>(null);
const [loadData, setLoadData] = React.useState(false);
useFocusEffect(
React.useCallback(() => {
onLoadData();
}, [id])
);
const onLoadData = async () => {
try {
setLoadData(true);
const responseData = await apiDonationGetOne({
id: id as string,
category: "permanent",
});
if (responseData.success) {
setData({
totalPencairan: responseData.data.totalPencairan,
akumulasiPencairan: responseData.data.akumulasiPencairan,
});
}
const responseList = await apiDonationDisbursementOfFundsListById({
id: id as string,
});
if (responseList.success) {
setListData(responseList.data);
}
} catch (error) {
console.log("[ERROR]", error);
} finally {
setLoadData(false);
}
};
return (
<>
<ViewWrapper>
@@ -20,47 +74,50 @@ export default function DonationFundDisbursement() {
<Grid>
<Grid.Col span={6}>
<TextCustom bold color="yellow">
Rp. 0
Rp. {formatCurrencyDisplay(data?.totalPencairan)}
</TextCustom>
<TextCustom size="small">Total Pencairan Dana</TextCustom>
</Grid.Col>
<Grid.Col span={6}>
<TextCustom bold color="yellow">
0 kali
{data?.akumulasiPencairan} kali
</TextCustom>
<TextCustom size="small">Akumulasi Pencairan</TextCustom>
</Grid.Col>
</Grid>
</BaseBox>
{Array.from({ length: 10 }).map((_, index) => (
<BaseBox key={index}>
<StackCustom>
<Grid>
<Grid.Col span={8}>
<TextCustom bold>Pencairan ke - {index + 1}</TextCustom>
</Grid.Col>
<Grid.Col span={4} style={{ alignItems: "flex-end" }}>
<TextCustom>{dayjs().format("DD MMM YYYY")}</TextCustom>
</Grid.Col>
</Grid>
<TextCustom>
Lorem ipsum dolor sit amet consectetur adipisicing elit.
Nesciunt dolor ad sit? Eaque rem nihil natus, id, esse possimus
perferendis provident velit illo consectetur distinctio ab
accusantium quis earum omnis!
</TextCustom>
<ButtonCenteredOnly
onPress={() => {
router.navigate(`/(application)/(file)/${id}`);
}}
icon="file-text"
>
Bukti Transaksi
</ButtonCenteredOnly>
</StackCustom>
</BaseBox>
))}
{loadData ? (
<LoaderCustom />
) : _.isEmpty(listData) ? (
<TextCustom align="center" color="gray">
Belum ada data
</TextCustom>
) : (
listData?.map((item, index) => (
<BaseBox key={index}>
<StackCustom>
<Grid>
<Grid.Col span={8}>
<TextCustom bold>{item?.title}</TextCustom>
</Grid.Col>
<Grid.Col span={4} style={{ alignItems: "flex-end" }}>
<TextCustom>{dayjs(item?.createdAt).format("DD MMM YYYY")}</TextCustom>
</Grid.Col>
</Grid>
<TextCustom>{item?.deskripsi}</TextCustom>
<ButtonCenteredOnly
onPress={() => {
router.navigate(`/(application)/(image)/preview-image/${item?.imageId}`);
}}
icon="file-text"
>
Bukti Transaksi
</ButtonCenteredOnly>
</StackCustom>
</BaseBox>
))
)}
</ViewWrapper>
</>
);

View File

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

View File

@@ -1,46 +1,93 @@
/* eslint-disable react-hooks/exhaustive-deps */
import {
BaseBox,
Grid,
LoaderCustom,
Spacing,
StackCustom,
TextCustom,
ViewWrapper,
} from "@/components";
import { MainColor } from "@/constants/color-palet";
import { apiAdminDonationListOfDonaturById } from "@/service/api-admin/api-admin-donation";
import { formatCurrencyDisplay } from "@/utils/formatCurrencyDisplay";
import { FontAwesome6 } from "@expo/vector-icons";
import dayjs from "dayjs";
import { useFocusEffect, useLocalSearchParams } from "expo-router";
import _ from "lodash";
import { useCallback, useState } from "react";
export default function Donation_ListOfDonatur() {
const { id } = useLocalSearchParams();
const [listData, setListData] = useState<any[] | null>(null);
const [loadData, setLoadData] = useState(false);
useFocusEffect(
useCallback(() => {
onLoadData();
}, [id])
);
const onLoadData = async () => {
try {
setLoadData(true);
const response = await apiAdminDonationListOfDonaturById({
id: id as string,
});
if (response.success) {
setListData(response.data);
}
} catch (error) {
console.log("[ERROR]", error);
} finally {
setLoadData(false);
}
};
return (
<>
<ViewWrapper>
{Array.from({ length: 10 }).map((_, index) => (
<BaseBox key={index}>
<Grid>
<Grid.Col
span={3}
style={{ alignItems: "center", justifyContent: "center" }}
>
<FontAwesome6
name="face-smile-wink"
size={50}
style={{ color: MainColor.yellow }}
/>
</Grid.Col>
<Grid.Col span={9}>
<StackCustom gap={"xs"}>
{loadData ? (
<LoaderCustom />
) : _.isEmpty(listData) ? (
<TextCustom bold align="center">
Belum ada donatur
</TextCustom>
) : (
listData?.map((item: any, index: number) => (
<BaseBox key={index}>
<Grid>
<Grid.Col
span={3}
style={{ alignItems: "center", justifyContent: "center" }}
>
<FontAwesome6
name="face-smile-wink"
size={50}
style={{ color: MainColor.yellow }}
/>
</Grid.Col>
<Grid.Col span={9}>
<TextCustom bold size="large">
Username
{item?.Author?.username || "-"}
</TextCustom>
<TextCustom>Berdonas sebesar </TextCustom>
<TextCustom bold size="large" color="yellow">
Rp. 100.000
</TextCustom>
<TextCustom>{dayjs().format("DD MMM YYYY")}</TextCustom>
</StackCustom>
</Grid.Col>
</Grid>
</BaseBox>
))}
<Spacing/>
<StackCustom gap={"xs"}>
<TextCustom size={"small"}>Berdonas sebesar </TextCustom>
<TextCustom bold size="large" color="yellow">
Rp. {formatCurrencyDisplay(item?.nominal)}
</TextCustom>
<TextCustom>
{dayjs(item?.createdAt).format("DD MMM YYYY, HH:mm")}
</TextCustom>
</StackCustom>
</Grid.Col>
</Grid>
</BaseBox>
))
)}
</ViewWrapper>
</>
);

View File

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

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 { DateTimePickerEvent } from "@react-native-community/datetimepicker";
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";
export default function EventEdit() {
@@ -55,6 +55,7 @@ export default function EventEdit() {
try {
setIsLoadData(true);
const response = await apiEventGetOne({ id: id as string });
console.log("[DATA BY ID]", JSON.stringify(response, null, 2));
if (response.success) {
setData(response.data);
setSelectedDate(new Date(response.data.tanggal));
@@ -209,7 +210,7 @@ export default function EventEdit() {
minimumDate={new Date(Date.now())}
label="Tanggal & Waktu Mulai"
required
value={selectedDate as any}
value={selectedDate}
onChange={(date: any) => {
setSelectedDate(date as any);
}}
@@ -254,7 +255,6 @@ export default function EventEdit() {
placeholder="Masukkan deskripsi event"
required
showCount
maxLength={100}
value={data?.deskripsi}
onChangeText={(value) => setData({ ...data, deskripsi: value })}
/>

View File

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

View File

@@ -55,6 +55,8 @@ export default function EventDetailPublish() {
userId: user?.id as string,
});
console.log("[RES CHECK PARTICIPANTS]", responseCheckParticipants);
if (
responseCheckParticipants.success &&
responseCheckParticipants.data
@@ -135,7 +137,7 @@ export default function EventDetailPublish() {
<>
<Stack.Screen
options={{
title: `Event publish`,
title: `Event Publish`,
headerLeft: () => <LeftButtonCustom />,
headerRight: () => <DotButton onPress={() => setOpenDrawer(true)} />,
}}

View File

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

View File

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

View File

@@ -1,142 +1,12 @@
/* eslint-disable react-hooks/exhaustive-deps */
import {
AvatarComp,
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";
import View_Forumku from "@/screens/Forum/ViewForumku";
import View_Forumku2 from "@/screens/Forum/ViewForumku2";
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 (
<>
<ViewWrapper
floatingButton={
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_Forumku /> */}
<View_Forumku2 />
</>
);
}

View File

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

View File

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

View File

@@ -1,129 +1,12 @@
/* eslint-disable react-hooks/exhaustive-deps */
import {
AvatarComp,
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";
import Forum_ViewBeranda from "@/screens/Forum/ViewBeranda";
import Forum_ViewBeranda2 from "@/screens/Forum/ViewBeranda2";
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 (
<>
<Stack.Screen
options={{
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>
{/* <Forum_ViewBeranda /> */}
<Forum_ViewBeranda2 />
</>
);
}

View File

@@ -0,0 +1,202 @@
import {
BaseBox,
ButtonCustom,
CheckboxCustom,
NewWrapper,
StackCustom,
TextCustom,
} from "@/components";
import { useAuth } from "@/hooks/use-auth";
import { apiAcceptForumTerms } from "@/service/api-client/api-user";
import { GStyles } from "@/styles/global-styles";
import { Ionicons } from "@expo/vector-icons";
import { router } from "expo-router";
import { useState } from "react";
import { View } from "react-native";
import { Text } from "react-native-paper";
import Toast from "react-native-toast-message";
export default function ForumSplash() {
const { user } = useAuth();
const [term, setTerm] = useState(false);
const [loading, setLoading] = useState(false);
const handleSubmit = async () => {
try {
setLoading(true);
const respone = await apiAcceptForumTerms({
category: "Forum",
userId: user?.id as any,
});
if (respone.success) {
Toast.show({
type: "success",
text1: "Berhasil",
text2: "Terima kasih telah menerima syarat & ketentuan forum ini",
});
router.replace("/(application)/forum");
return;
}
Toast.show({
type: "error",
text1: "Gagal",
text2: "Terjadi kesalahan, silahkan coba lagi",
});
} catch (error) {
console.log("[ERROR]", error);
} finally {
setLoading(false);
}
};
return (
<NewWrapper>
{/* <TextCustom bold>HIPMI Badung Connect</TextCustom> . */}
<BaseBox>
<StackCustom>
<TextCustom>
Dengan mengakses dan menggunakan Forum HIPMI Badung Connect, Anda
secara sadar menyetujui ketentuan berikut:
</TextCustom>
<TextCustom bold>
1. Dilarang keras memposting konten yang mengandung:
</TextCustom>
<View style={{ paddingInline: 10 }}>
{forumTerms1.map((term, index) => (
<View
key={index}
style={{
flexDirection: "row",
alignItems: "center",
gap: 10,
paddingBottom: 10,
}}
>
<Ionicons name="radio-button-on" color={"white"} />
<TextCustom>{term.text}</TextCustom>
</View>
))}
</View>
<TextCustom bold>
2. Setiap pengguna bertanggung jawab penuh atas konten yang
diunggah. Konten yang melanggar ketentuan ini dapat dihapus kapan
saja tanpa pemberitahuan.
</TextCustom>
<TextCustom bold>
3. Jika Anda menemukan konten tidak pantas, segera:
</TextCustom>
<View style={{ paddingInline: 10 }}>
{forumTerms2.map((term, index) => (
<View
key={index}
style={{
flexDirection: "row",
alignItems: "center",
gap: 10,
paddingBottom: 10,
}}
>
<Ionicons name="radio-button-on" color={"white"} />
<TextCustom>{term.text}</TextCustom>
</View>
))}
</View>
<TextCustom bold>
4. Gunakan fitur Blokir Pengguna di profil pengguna terkait
</TextCustom>
<View style={{ paddingInline: 10 }}>
{forumTerms3.map((term, index) => (
<View
key={index}
style={{
flexDirection: "row",
alignItems: "center",
gap: 10,
paddingBottom: 10,
}}
>
<Ionicons name="radio-button-on" color={"white"} />
<TextCustom>{term.text}</TextCustom>
</View>
))}
</View>
<TextCustom>
Pelanggaran terhadap ketentuan ini berakibat{" "}
<TextCustom bold>pencabutan akses</TextCustom> ke Forum dan/atau{" "}
<TextCustom bold>pemblokiran akun secara permanen</TextCustom> tanpa
pemberitahuan lebih lanjut.
</TextCustom>
<View
style={{
flexDirection: "row",
alignItems: "center",
marginTop: 16,
marginBottom: 16,
}}
>
<CheckboxCustom value={term} onChange={() => setTerm(!term)} />
<Text style={GStyles.textLabel}>
Saya telah membaca dan menyetujui Syarat & Ketentuan Forum ini
</Text>
</View>
<ButtonCustom
disabled={!term || loading}
onPress={() => {
handleSubmit();
}}
>
Lanjut
</ButtonCustom>
</StackCustom>
</BaseBox>
</NewWrapper>
);
}
// Data dalam format JSON (bisa juga diimpor dari file terpisah)
interface Term {
text: string;
}
const forumTerms1: Term[] = [
{
text: "Ujaran kebencian, diskriminasi, atau konten SARA (Suku, Agama, Ras, Antar-golongan)",
},
{ text: "Kata kasar, pelecehan, ancaman, atau bullying" },
{ text: "Pornografi, hoaks, spam, atau informasi menyesatkan" },
{ text: "Promosi aktivitas ilegal seperti perjudian atau narkoba" },
];
const forumTerms2: Term[] = [
{
text: "Gunakan tombol “Laporkan” di setiap postingan, atau",
},
{
text: "Gunakan fitur “Blokir Pengguna” di profil pengguna terkait.",
},
];
const forumTerms3: Term[] = [
{
text: "Meninjau setiap laporan dalam waktu 24 jam",
},
{
text: "Menghapus konten yang melanggar",
},
{
text: "Memblokir akun pelanggar sesuai tingkat pelanggaran",
},
];

View File

@@ -3,7 +3,9 @@
import { StackCustom, ViewWrapper } from "@/components";
import { MainColor } from "@/constants/color-palet";
import { useAuth } from "@/hooks/use-auth";
import { useNotificationStore } from "@/hooks/use-notification-store";
import Home_BottomFeatureSection from "@/screens/Home/bottomFeatureSection";
import HeaderBell from "@/screens/Home/HeaderBell";
import Home_ImageSection from "@/screens/Home/imageSection";
import TabSection from "@/screens/Home/tabSection";
import { tabsHome } from "@/screens/Home/tabsList";
@@ -11,31 +13,52 @@ import Home_FeatureSection from "@/screens/Home/topFeatureSection";
import { apiUser } from "@/service/api-client/api-user";
import { apiVersion } from "@/service/api-config";
import { Ionicons } from "@expo/vector-icons";
import { Redirect, router, Stack } from "expo-router";
import { useEffect, useState } from "react";
import { Redirect, router, Stack, useFocusEffect } from "expo-router";
import { useCallback, useState } from "react";
import { RefreshControl } from "react-native";
export default function Application() {
const { token, user } = useAuth();
const { token, user, userData } = useAuth();
const [data, setData] = useState<any>();
const [refreshing, setRefreshing] = useState(false);
const { syncUnreadCount } = useNotificationStore();
useEffect(() => {
onLoadData();
checkVersion();
}, []);
useFocusEffect(
useCallback(() => {
onLoadData();
checkVersion();
userData(token as string);
syncUnreadCount();
}, [user?.id, token])
);
async function onLoadData() {
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);
}
const checkVersion = async () => {
const response = await apiVersion();
console.log("Version >>", JSON.stringify(response.data, null, 2));
console.log("[Version] >>", JSON.stringify(response.data, null, 2));
};
const onRefresh = useCallback(() => {
setRefreshing(true);
onLoadData();
checkVersion();
setRefreshing(false);
}, []);
// if (user && user?.termsOfServiceAccepted === false) {
// console.log("User is not accept term service");
// return <Redirect href={`/terms-agreement`} />;
// }
if (data && data?.active === false) {
console.log("User is not active");
return <Redirect href={`/waiting-room`} />;
@@ -61,24 +84,27 @@ export default function Application() {
}}
/>
),
headerRight: () => (
<Ionicons
name="notifications"
size={20}
color={MainColor.yellow}
onPress={() => {
router.push("/notifications");
}}
/>
),
headerRight: () => <HeaderBell />,
}}
/>
<ViewWrapper
refreshControl={
<RefreshControl refreshing={refreshing} onRefresh={onRefresh} />
}
footerComponent={
<TabSection tabs={tabsHome(data?.Profile?.id as string)} />
<TabSection
tabs={tabsHome({
acceptedForumTermsAt: data?.acceptedForumTermsAt,
profileId: data?.Profile?.id,
})}
/>
}
>
<StackCustom>
{/* <ButtonCustom onPress={() => router.push("./test-notifications")}>
Test Notif
</ButtonCustom> */}
<Home_ImageSection />
<Home_FeatureSection />

View File

@@ -1,23 +1,14 @@
import {
BaseBox,
FloatingButton,
Grid,
LoaderCustom,
ProgressCustom,
StackCustom,
TextCustom,
ViewWrapper,
ViewWrapper
} from "@/components";
import API_STRORAGE from "@/constants/base-url-api-strorage";
import DUMMY_IMAGE from "@/constants/dummy-image-value";
import NoDataText from "@/components/_ShareComponent/NoDataText";
import Investment_BoxBerandaSection from "@/screens/Invesment/BoxBerandaSection";
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 _ from "lodash";
import { useCallback, useState } from "react";
import { View } from "react-native";
export default function InvestmentBursa() {
const [list, setList] = useState<any[] | null>(null);
@@ -32,8 +23,10 @@ export default function InvestmentBursa() {
const onLoadList = async () => {
try {
setLoadingList(true);
const response = await apiInvestmentGetAll();
console.log("[DATA LIST]", JSON.stringify(response.data, null, 2));
const response = await apiInvestmentGetAll({
category: "bursa"
});
// console.log("[DATA LIST]", JSON.stringify(response.data, null, 2));
setList(response.data);
} catch (error) {
console.log("[ERROR]", error);
@@ -52,95 +45,12 @@ export default function InvestmentBursa() {
{loadingList ? (
<LoaderCustom />
) : _.isEmpty(list) ? (
<TextCustom>Belum ada data</TextCustom>
<NoDataText />
) : (
list?.map((item: any, index: number) => (
<BaseBox
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>
<Investment_BoxBerandaSection id={item.id} data={item} key={index} />
))
)}
</ViewWrapper>
);
}
// <View style={{ padding: 20, gap: 16 }}>
// <TextCustom>Progress 70%</TextCustom>
// <ProgressCustom value={70} color="primary" size="md" />
// <TextCustom>Success Progress</TextCustom>
// <ProgressCustom value={40} color="success" size="lg" />
// <TextCustom>Warning Progress (small)</TextCustom>
// <ProgressCustom value={90} color="warning" size="sm" />
// <TextCustom>Error Indeterminate</TextCustom>
// <ProgressCustom value={null} color="error" size="md" />
// <TextCustom>Custom Radius</TextCustom>
// <ProgressCustom value={60} color="info" size="xl" radius={4} />
// <ProgressCustom value={70} color="primary" size="lg" />
// <ProgressCustom value={45} color="success" size="md" label="Halfway!" />
// <ProgressCustom value={90} color="warning" size="lg" showLabel={false} />
// <ProgressCustom value={null} color="error" size="sm" label="Loading..." />
// </View>;

View File

@@ -1,50 +1,102 @@
/* eslint-disable react-hooks/exhaustive-deps */
import {
BaseBox,
Grid,
LoaderCustom,
ProgressCustom,
Spacing,
StackCustom,
TextCustom,
ViewWrapper,
} from "@/components";
import { router } from "expo-router";
import NoDataText from "@/components/_ShareComponent/NoDataText";
import { useAuth } from "@/hooks/use-auth";
import {
apiInvestmentGetAll
} from "@/service/api-client/api-investment";
import { formatCurrencyDisplay } from "@/utils/formatCurrencyDisplay";
import { router, useFocusEffect } from "expo-router";
import _ from "lodash";
import React, { useCallback, useState } from "react";
import { View } from "react-native";
export default function InvestmentMyHolding() {
const { user } = useAuth();
const [list, setList] = useState<any[] | null>(null);
const [loadingList, setLoadingList] = useState(false);
useFocusEffect(
useCallback(() => {
onLoadList();
}, [user?.id])
);
const onLoadList = async () => {
try {
setLoadingList(true);
const response = await apiInvestmentGetAll({
category: "my-holding",
authorId: user?.id,
});
console.log("[DATA LIST]", JSON.stringify(response.data, null, 2));
setList(response.data);
} catch (error) {
console.log("[ERROR]", error);
} finally {
setLoadingList(false);
}
};
return (
<ViewWrapper hideFooter>
{Array.from({ length: 10 }).map((_, index) => (
<BaseBox key={index} paddingTop={7} paddingBottom={7} onPress={() => router.push(`/investment/${index}/(my-holding)/holding-${index}`)}>
<Grid>
<Grid.Col span={6}>
<StackCustom gap={"xs"}>
<TextCustom truncate={2}>
Title here : Lorem ipsum dolor sit amet consectetur
adipisicing elit. Omnis, exercitationem, sequi enim quod
distinctio maiores laudantium amet, quidem atque repellat sit
vitae qui aliquam est veritatis laborum eum voluptatum totam!
</TextCustom>
{loadingList ? (
<LoaderCustom />
) : _.isEmpty(list) ? (
<NoDataText />
) : (
list?.map((item, index) => (
<BaseBox
key={index}
paddingTop={7}
paddingBottom={7}
onPress={() =>
router.push(`/investment/${item?.id}/(my-holding)/${item?.id}`)
}
>
<Grid>
<Grid.Col span={6}>
<StackCustom gap={"xs"}>
<TextCustom truncate={2}>{item?.title}</TextCustom>
<Spacing height={5} />
<TextCustom size="small">Rp. 7.500.000</TextCustom>
<TextCustom size="small">300 Lembar</TextCustom>
</StackCustom>
</Grid.Col>
<Grid.Col span={1}>
<View />
</Grid.Col>
<Grid.Col
span={5}
style={{
justifyContent: "center",
alignItems: "center",
}}
>
<ProgressCustom value={(index % 5) * 20} size="lg" />
</Grid.Col>
</Grid>
</BaseBox>
))}
<Spacing height={5} />
<TextCustom size="small">
Rp. {formatCurrencyDisplay(item?.nominal)}
</TextCustom>
<TextCustom size="small">
{item?.lembarTerbeli} Lembar
</TextCustom>
</StackCustom>
</Grid.Col>
<Grid.Col span={1}>
<View />
</Grid.Col>
<Grid.Col
span={5}
style={{
justifyContent: "center",
alignItems: "center",
}}
>
<ProgressCustom
value={item?.progress}
label={`${item?.progress}%`}
size="lg"
/>
</Grid.Col>
</Grid>
</BaseBox>
))
)}
</ViewWrapper>
);
}

View File

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

View File

@@ -1,29 +1,56 @@
/* eslint-disable react-hooks/exhaustive-deps */
import {
BackButton,
BaseBox,
DotButton,
DrawerCustom,
Grid,
MenuDrawerDynamicGrid,
StackCustom,
TextCustom,
ViewWrapper,
BackButton,
BaseBox,
DotButton,
DrawerCustom,
Grid,
MenuDrawerDynamicGrid,
StackCustom,
TextCustom,
ViewWrapper,
} from "@/components";
import { IconDocument, IconEdit, IconNews } from "@/components/_Icon";
import { IMenuDrawerItem } from "@/components/_Interface/types";
import { MainColor } from "@/constants/color-palet";
import { ICON_SIZE_MEDIUM } from "@/constants/constans-value";
import { useAuth } from "@/hooks/use-auth";
import Invesment_ComponentBoxOnBottomDetail from "@/screens/Invesment/ComponentBoxOnBottomDetail";
import Invesment_DetailDataPublishSection from "@/screens/Invesment/DetailDataPublishSection";
import { apiInvestmentGetInvoice } from "@/service/api-client/api-investment";
import { formatCurrencyDisplay } from "@/utils/formatCurrencyDisplay";
import { AntDesign, MaterialIcons } from "@expo/vector-icons";
import { router, Stack, useLocalSearchParams } from "expo-router";
import { router, Stack, useFocusEffect, useLocalSearchParams } from "expo-router";
import _ from "lodash";
import { useState } from "react";
import { useCallback, useState } from "react";
export default function InvestmentDetailHolding() {
const { user } = useAuth();
const { id, status } = useLocalSearchParams();
const [openDrawerDraft, setOpenDrawerDraft] = useState(false);
const [openDrawerPublish, setOpenDrawerPublish] = useState(false);
const [data, setData] = useState<any>(null);
useFocusEffect(
useCallback(() => {
onLoadData();
}, [id, status])
);
const onLoadData = async () => {
try {
const response = await apiInvestmentGetInvoice({
id: id as string,
authorId: user?.id,
category: "invoice",
});
console.log("[DATA]", JSON.stringify(response.data, null, 2));
setData(response.data);
} catch (error) {
console.log("[ERROR]", error);
}
};
const handlePressDraft = (item: IMenuDrawerItem) => {
console.log("PATH >> ", item.path);
@@ -39,7 +66,8 @@ export default function InvestmentDetailHolding() {
const bottomSection = (
<Invesment_ComponentBoxOnBottomDetail
id={id as string}
prospectusId={id as string}
id={data?.Investasi?.id as string}
status={"publish"}
/>
);
@@ -64,10 +92,12 @@ export default function InvestmentDetailHolding() {
<StackCustom gap={"xs"}>
<Grid>
<Grid.Col span={6}>
<TextCustom bold>Nila Transaksi</TextCustom>
<TextCustom bold>Nilai Transaksi</TextCustom>
</Grid.Col>
<Grid.Col span={6}>
<TextCustom bold>Rp. 7.500.000</TextCustom>
<TextCustom bold>
Rp. {data ? formatCurrencyDisplay(data?.nominal) : ""}
</TextCustom>
</Grid.Col>
</Grid>
<Grid>
@@ -75,12 +105,16 @@ export default function InvestmentDetailHolding() {
<TextCustom bold>Saham Terbeli</TextCustom>
</Grid.Col>
<Grid.Col span={6}>
<TextCustom bold>300 Lembar</TextCustom>
<TextCustom bold>
{data ? data?.lembarTerbeli : ""} Lembar
</TextCustom>
</Grid.Col>
</Grid>
</StackCustom>
</BaseBox>
<Invesment_DetailDataPublishSection
data={data && data?.Investasi}
status={"publish"}
bottomSection={bottomSection}
/>

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

View File

@@ -108,7 +108,9 @@ export default function InvestmentInvest() {
<TextCustom>Sisa Lembar Saham</TextCustom>
</Grid.Col>
<Grid.Col span={6} style={{ alignItems: "flex-end" }}>
<TextCustom>{data?.sisaLembar || "-"}</TextCustom>
<TextCustom>
{data && formatCurrencyDisplay(data?.sisaLembar) || "-"}
</TextCustom>
</Grid.Col>
</Grid>
<Grid>
@@ -116,7 +118,9 @@ export default function InvestmentInvest() {
<TextCustom>Harga Per Lembar</TextCustom>
</Grid.Col>
<Grid.Col span={6} style={{ alignItems: "flex-end" }}>
<TextCustom>{data?.hargaLembar || "-"}</TextCustom>
<TextCustom>
{data && formatCurrencyDisplay(data?.hargaLembar) || "-"}
</TextCustom>
</Grid.Col>
</Grid>
<Grid>

View File

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

View File

@@ -15,6 +15,7 @@ import Investment_ButtonInvestasiSection from "@/screens/Invesment/ButtonInvesta
import Invesment_ComponentBoxOnBottomDetail from "@/screens/Invesment/ComponentBoxOnBottomDetail";
import Invesment_DetailDataPublishSection from "@/screens/Invesment/DetailDataPublishSection";
import { apiInvestmentGetOne } from "@/service/api-client/api-investment";
import { countDownAndCondition } from "@/utils/countDownAndCondition";
import { AntDesign, MaterialIcons } from "@expo/vector-icons";
import {
router,
@@ -23,7 +24,7 @@ import {
useLocalSearchParams,
} from "expo-router";
import _ from "lodash";
import { useCallback, useState } from "react";
import { useCallback, useEffect, useState } from "react";
export default function InvestmentDetail() {
const { user } = useAuth();
@@ -62,6 +63,31 @@ export default function InvestmentDetail() {
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 = (
<Invesment_ComponentBoxOnBottomDetail
id={id as string}
@@ -71,7 +97,7 @@ export default function InvestmentDetail() {
);
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 (

View File

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

View File

@@ -1,34 +1,72 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { BackButton } from "@/components";
import { IconHome, IconStatus } from "@/components/_Icon";
import { TabsStyles } from "@/styles/tabs-styles";
import { Ionicons } from "@expo/vector-icons";
import { Tabs } from "expo-router";
import {
router,
Tabs,
useLocalSearchParams,
useNavigation
} from "expo-router";
import { useLayoutEffect } from "react";
export default function JobTabsLayout() {
const navigation = useNavigation();
const { from, category } = useLocalSearchParams<{
from?: string;
category?: string;
}>();
// Atur header secara dinamis
useLayoutEffect(() => {
navigation.setOptions({
headerLeft: () => (
<BackButton
onPress={() => {
if (from === "notifications") {
router.replace(`/notifications?category=${category}`);
} else {
if (from) {
router.replace(`/${from}` as any);
} else {
router.navigate("/home");
}
}
}}
/>
),
});
}, [from, router, navigation]);
return (
<Tabs screenOptions={TabsStyles}>
<Tabs.Screen
name="index"
options={{
title: "Beranda",
tabBarIcon: ({ color }) => <IconHome color={color} />,
}}
/>
<Tabs.Screen
name="status"
options={{
title: "Status",
tabBarIcon: ({ color }) => <IconStatus color={color} />,
}}
/>
<Tabs.Screen
name="archive"
options={{
title: "Arsip",
tabBarIcon: ({ color }) => (
<Ionicons size={20} name="archive" color={color} />
),
}}
/>
</Tabs>
<>
<Tabs screenOptions={TabsStyles}>
<Tabs.Screen
name="index"
options={{
title: "Beranda",
tabBarIcon: ({ color }) => <IconHome color={color} />,
}}
/>
<Tabs.Screen
name="status"
options={{
title: "Status",
tabBarIcon: ({ color }) => <IconStatus color={color} />,
}}
/>
<Tabs.Screen
name="archive"
options={{
title: "Arsip",
tabBarIcon: ({ color }) => (
<Ionicons size={20} name="archive" color={color} />
),
}}
/>
</Tabs>
</>
);
}

View File

@@ -9,14 +9,17 @@ import {
import { useAuth } from "@/hooks/use-auth";
import { dummyMasterStatus } from "@/lib/dummy-data/_master/status";
import { apiJobGetByStatus } from "@/service/api-client/api-job";
import { useFocusEffect } from "expo-router";
import { useFocusEffect, useLocalSearchParams } from "expo-router";
import _ from "lodash";
import { useCallback, useState } from "react";
export default function JobStatus() {
const { user } = useAuth();
const { status } = useLocalSearchParams<{ status?: string }>();
console.log("STATUS", status);
const [activeCategory, setActiveCategory] = useState<string | null>(
"publish"
status || "publish"
);
const [listData, setListData] = useState<any[]>([]);
const [isLoadList, setIsLoadList] = useState(false);
@@ -60,25 +63,29 @@ export default function JobStatus() {
);
return (
<ViewWrapper headerComponent={scrollComponent} hideFooter>
{isLoadList ? (
<LoaderCustom />
) : _.isEmpty(listData) ? (
<TextCustom align="center">Tidak ada data {activeCategory}</TextCustom>
) : (
listData.map((e, i) => (
<BaseBox
key={i}
paddingTop={20}
paddingBottom={20}
href={`/job/${e?.id}/${activeCategory}/detail`}
>
<TextCustom align="center" bold truncate size="large">
{e?.title}
</TextCustom>
</BaseBox>
))
)}
</ViewWrapper>
<>
<ViewWrapper headerComponent={scrollComponent} hideFooter>
{isLoadList ? (
<LoaderCustom />
) : _.isEmpty(listData) ? (
<TextCustom align="center">
Tidak ada data {activeCategory}
</TextCustom>
) : (
listData.map((e, i) => (
<BaseBox
key={i}
paddingTop={20}
paddingBottom={20}
href={`/job/${e?.id}/${activeCategory}/detail`}
>
<TextCustom align="center" bold truncate size="large">
{e?.title}
</TextCustom>
</BaseBox>
))
)}
</ViewWrapper>
</>
);
}

View File

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

View File

@@ -25,6 +25,8 @@ export default function JobDetail() {
setIsLoading(true);
const response = await apiJobGetOne({ id: id as string });
console.log("DATA", JSON.stringify(response.data, null,2));
setData(response.data);
} catch (error) {
console.log("[ERROR]", error);

View File

@@ -19,7 +19,7 @@ import { useState } from "react";
import Toast from "react-native-toast-message";
export default function JobCreate() {
const nextUrl = "/(application)/(user)/job/(tabs)/status";
const nextUrl = "/(application)/(user)/job/(tabs)/status?status=review";
const { user } = useAuth();
const [isLoading, setIsLoading] = useState(false);
const [image, setImage] = useState<string | null>(null);

View File

@@ -1,54 +1,226 @@
/* eslint-disable react-hooks/exhaustive-deps */
import {
BaseBox,
BoxButtonOnFooter,
ButtonCenteredOnly,
ButtonCustom,
InformationBox,
LandscapeFrameUploaded,
MapCustom,
Spacing,
TextInputCustom,
ViewWrapper
ViewWrapper,
} from "@/components";
import { router, useLocalSearchParams } from "expo-router";
import API_IMAGE from "@/constants/api-storage";
import DIRECTORY_ID from "@/constants/directory-id";
import { apiMapsGetOne, apiMapsUpdate } from "@/service/api-client/api-maps";
import { uploadFileService } from "@/service/upload-service";
import pickFile, { IFileData } from "@/utils/pickFile";
import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
import { useCallback, useState } from "react";
import { StyleSheet, View } from "react-native";
import MapView, { LatLng, Marker } from "react-native-maps";
import Toast from "react-native-toast-message";
const defaultRegion = {
latitude: -8.737109,
longitude: 115.1756897,
latitudeDelta: 0.1,
longitudeDelta: 0.1,
};
export default function MapsEdit() {
const { id } = useLocalSearchParams();
const [data, setData] = useState<any | null>({
id: "",
namePin: "",
latitude: "",
longitude: "",
imageId: "",
});
const [selectedLocation, setSelectedLocation] = useState<LatLng | null>(null);
const [image, setImage] = useState<IFileData | null>(null);
const [isLoading, setLoading] = useState(false);
useFocusEffect(
useCallback(() => {
onLoadData();
}, [id])
);
const onLoadData = async () => {
try {
const response = await apiMapsGetOne({ id: id as string });
if (response.success) {
setData({
id: response.data.id,
namePin: response.data.namePin,
latitude: response.data.latitude,
longitude: response.data.longitude,
imageId: response.data.imageId,
});
}
} catch (error) {
console.log("[ERROR]", error);
}
};
const handleMapPress = (event: any) => {
const { latitude, longitude } = event.nativeEvent.coordinate;
const location = { latitude, longitude };
setSelectedLocation(location);
};
const handleSubmit = async () => {
let newData: any;
if (!data.namePin) {
Toast.show({
type: "error",
text1: "Nama pin harus diisi",
});
return;
}
newData = {
namePin: data?.namePin,
latitude: selectedLocation?.latitude || data?.latitude,
longitude: selectedLocation?.longitude || data?.longitude,
};
try {
setLoading(true);
if (image) {
const responseUpload = await uploadFileService({
dirId: DIRECTORY_ID.map_image,
imageUri: image?.uri,
});
if (!responseUpload?.data?.id) {
Toast.show({
type: "error",
text1: "Gagal mengunggah gambar",
});
return;
}
const imageId = responseUpload?.data?.id;
newData = {
namePin: data?.namePin,
latitude: selectedLocation?.latitude,
longitude: selectedLocation?.longitude,
newImageId: imageId,
};
}
const responseUpdate = await apiMapsUpdate({
id: data?.id,
data: newData,
});
if (!responseUpdate.success) {
Toast.show({
type: "error",
text1: "Gagal mengupdate map",
});
return;
}
Toast.show({
type: "success",
text1: "Map berhasil diupdate",
});
router.back();
} catch (error) {
console.log("[ERROR]", error);
} finally {
setLoading(false);
}
};
const buttonFooter = (
<BoxButtonOnFooter>
<ButtonCustom
onPress={() => {
console.log(`Simpan maps ${id}`);
router.back()
}}
disabled={!data.namePin}
onPress={handleSubmit}
isLoading={isLoading}
>
Simpan
Update
</ButtonCustom>
</BoxButtonOnFooter>
);
return (
<ViewWrapper footerComponent={buttonFooter}>
<InformationBox text="Tentukan lokasi pin map dengan menekan pada map." />
<BaseBox>
<MapCustom />
</BaseBox>
<View style={[styles.container, { height: 400 }]}>
<MapView
style={styles.map}
initialRegion={
data?.latitude && data?.longitude
? {
latitude: data?.latitude,
longitude: data?.longitude,
latitudeDelta: 0.1,
longitudeDelta: 0.1,
}
: defaultRegion
}
onPress={handleMapPress}
showsUserLocation={true}
showsMyLocationButton={true}
loadingEnabled={true}
loadingIndicatorColor="#666"
loadingBackgroundColor="#f0f0f0"
>
{selectedLocation ? (
<Marker
coordinate={selectedLocation}
title="Lokasi Dipilih"
description={`Lat: ${selectedLocation.latitude.toFixed(
6
)}, Lng: ${selectedLocation.longitude.toFixed(6)}`}
pinColor="red"
/>
) : (
<Marker
coordinate={defaultRegion}
title="Lokasi Dipilih"
description={`Lat: ${defaultRegion.latitude.toFixed(
6
)}, Lng: ${defaultRegion.longitude.toFixed(6)}`}
pinColor="red"
/>
)}
</MapView>
</View>
<TextInputCustom
required
label="Nama Pin"
placeholder="Masukkan nama pin maps"
value={data?.namePin}
onChangeText={(value) => setData({ ...data, namePin: value })}
/>
<Spacing />
<InformationBox text="Upload foto lokasi bisnis anda untuk ditampilkan dalam detail maps." />
<LandscapeFrameUploaded />
<LandscapeFrameUploaded
image={
image
? image?.uri
: API_IMAGE.GET({ fileId: data?.imageId as string })
}
/>
<ButtonCenteredOnly
icon="upload"
onPress={() => {
console.log("Upload foto ");
router.navigate(`/take-picture/${id}`);
pickFile({
allowedType: "image",
setImageUri(file) {
setImage(file);
},
});
}}
>
Upload
@@ -57,3 +229,16 @@ export default function MapsEdit() {
</ViewWrapper>
);
}
const styles = StyleSheet.create({
container: {
width: "100%",
backgroundColor: "#f5f5f5",
overflow: "hidden",
borderRadius: 8,
marginBottom: 20,
},
map: {
flex: 1,
},
});

View File

@@ -6,21 +6,96 @@ import {
InformationBox,
LandscapeFrameUploaded,
Spacing,
TextCustom,
TextInputCustom,
ViewWrapper,
} from "@/components";
import MapSelected from "@/components/Map/MapSelected";
import DIRECTORY_ID from "@/constants/directory-id";
import { useAuth } from "@/hooks/use-auth";
import { apiMapsCreate } from "@/service/api-client/api-maps";
import { uploadFileService } from "@/service/upload-service";
import pickFile, { IFileData } from "@/utils/pickFile";
import { router, useLocalSearchParams } from "expo-router";
import { useState } from "react";
import { LatLng } from "react-native-maps";
import Toast from "react-native-toast-message";
export default function MapsCreate() {
const { user } = useAuth();
const { id } = useLocalSearchParams();
const [selectedLocation, setSelectedLocation] = useState<LatLng | null>(null);
const [name, setName] = useState<string>("");
const [image, setImage] = useState<IFileData | null>(null);
const [isLoading, setLoading] = useState(false);
const handleSubmit = async () => {
try {
setLoading(true);
let newData: any;
newData = {
authorId: user?.id,
portofolioId: id,
namePin: name,
latitude: selectedLocation?.latitude,
longitude: selectedLocation?.longitude,
};
if (image) {
const responseUpload = await uploadFileService({
dirId: DIRECTORY_ID.map_image,
imageUri: image?.uri,
});
if (!responseUpload?.data?.id) {
Toast.show({
type: "error",
text1: "Gagal mengunggah gambar",
});
return;
}
const imageId = responseUpload?.data?.id;
newData = {
authorId: user?.id,
portofolioId: id,
namePin: name,
latitude: selectedLocation?.latitude,
longitude: selectedLocation?.longitude,
imageId: imageId,
};
}
const response = await apiMapsCreate({
data: newData,
});
if (!response.success) {
Toast.show({
type: "error",
text1: "Gagal menambahkan map",
});
return;
}
Toast.show({
type: "success",
text1: "Map berhasil ditambahkan",
});
router.back();
} catch (error) {
console.log("[ERROR]", error);
} finally {
setLoading(false);
}
};
const buttonFooter = (
<BoxButtonOnFooter>
<ButtonCustom
onPress={() => {
console.log(`Simpan maps ${id}`);
router.replace(`/portofolio/${id}`);
}}
isLoading={isLoading}
disabled={!selectedLocation || name === ""}
onPress={handleSubmit}
>
Simpan
</ButtonCustom>
@@ -30,25 +105,34 @@ export default function MapsCreate() {
<ViewWrapper footerComponent={buttonFooter}>
<InformationBox text="Tentukan lokasi pin map dengan menekan pada map." />
<BaseBox style={{ height: 400 }}>
<TextCustom>Maps Her</TextCustom>
<BaseBox>
<MapSelected
selectedLocation={selectedLocation as any}
setSelectedLocation={setSelectedLocation}
/>
</BaseBox>
<TextInputCustom
required
label="Nama Pin"
placeholder="Masukkan nama pin maps"
value={name}
onChangeText={setName}
/>
<Spacing height={50} />
<InformationBox text="Upload foto lokasi bisnis anda untuk ditampilkan dalam detail maps." />
<LandscapeFrameUploaded />
<LandscapeFrameUploaded image={image?.uri} />
<ButtonCenteredOnly
icon="upload"
onPress={() => {
console.log("Upload foto ");
router.navigate(`/take-picture/${id}`);
pickFile({
allowedType: "image",
setImageUri(file) {
setImage(file);
},
});
}}
>
Upload

View File

@@ -1,19 +1,21 @@
import { MapCustom, ViewWrapper } from "@/components";
import { View } from "react-native";
import MapView from "react-native-maps";
import MapsView from "@/screens/Maps/MapsView";
import MapsView2 from "@/screens/Maps/MapsView2";
import { Text, View } from "react-native";
export interface LocationItem {
id: string | number;
latitude: number;
longitude: number;
name: string;
imageId?: string;
}
export default function Maps() {
return (
<ViewWrapper style={{ paddingInline: 0, paddingBlock: 0 }}>
{/* <MapCustom height={"100%"} /> */}
<View style={{ flex: 1 }}>
<MapView
style={{
width: "100%",
height: "100%",
}}
/>
</View>
</ViewWrapper>
<>
<MapsView />
{/* <MapsView2 />, */}
{/* <View style={{ flex: 1, backgroundColor: "gray" }}><Text style={{ color: "white" }}>Map disabled</Text></View> */}
</>
);
}

View File

@@ -1,79 +1,93 @@
/* eslint-disable react-hooks/exhaustive-deps */
import {
BaseBox,
Grid,
NewWrapper,
ScrollableCustom,
StackCustom,
TextCustom,
ViewWrapper,
} from "@/components";
import { MainColor } from "@/constants/color-palet";
import { useState } from "react";
import { View } from "react-native";
const categories = [
{ value: "all", label: "Semua" },
{ value: "event", label: "Event" },
{ value: "job", label: "Job" },
{ value: "voting", label: "Voting" },
{ value: "donasi", label: "Donasi" },
{ value: "investasi", label: "Investasi" },
{ value: "forum", label: "Forum" },
{ value: "collaboration", label: "Collaboration" },
];
import ListSkeletonComponent from "@/components/_ShareComponent/ListSkeletonComponent";
import NoDataText from "@/components/_ShareComponent/NoDataText";
import { AccentColor } from "@/constants/color-palet";
import { useAuth } from "@/hooks/use-auth";
import { useNotificationStore } from "@/hooks/use-notification-store";
import { apiGetNotificationsById } from "@/service/api-notifications";
import { listOfcategoriesAppNotification } from "@/types/type-notification-category";
import { formatChatTime } from "@/utils/formatChatTime";
import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
import _ from "lodash";
import { useCallback, useState } from "react";
import { RefreshControl, View } from "react-native";
const selectedCategory = (value: string) => {
const category = categories.find((c) => c.value === value);
const category = listOfcategoriesAppNotification.find(
(c) => c.value === value
);
return category?.label;
};
const fixPath = ({
deepLink,
categoryApp,
}: {
deepLink: string;
categoryApp: string;
}) => {
if (categoryApp === "OTHER") {
return deepLink;
}
const separator = deepLink.includes("?") ? "&" : "?";
const fixedPath =
`${deepLink}${separator}from=notifications&category=${_.lowerCase(categoryApp)}`;
console.log("Fix Path", fixedPath);
return fixedPath;
};
const BoxNotification = ({
index,
data,
activeCategory,
}: {
index: number;
data: any;
activeCategory: string | null;
}) => {
// console.log("DATA NOTIFICATION", JSON.stringify(data, null, 2));
const { markAsRead } = useNotificationStore();
return (
<>
<BaseBox
onPress={() =>
console.log(
"Notification >",
selectedCategory(activeCategory as string)
)
}
backgroundColor={data.isRead ? AccentColor.darkblue : AccentColor.blue}
onPress={() => {
// console.log(
// "Notification >",
// selectedCategory(activeCategory as string)
// );
const newPath = fixPath({
deepLink: data.deepLink,
categoryApp: data.kategoriApp,
});
router.replace(newPath as any);
selectedCategory(activeCategory as string);
if (!data.isRead) {
markAsRead(data.id);
}
}}
>
<StackCustom>
<TextCustom bold>
# {selectedCategory(activeCategory as string)}
<TextCustom truncate={2} bold>
{data.title}
</TextCustom>
<View
style={{
borderBottomColor: MainColor.white_gray,
borderBottomWidth: 1,
}}
/>
<TextCustom truncate={2}>{data.pesan}</TextCustom>
<TextCustom truncate={2}>
Lorem ipsum dolor sit amet consectetur adipisicing elit. Sint odio
unde quidem voluptate quam culpa sequi molestias ipsa corrupti id,
soluta, nostrum adipisci similique, et illo asperiores deleniti eum
labore.
<TextCustom size="small" color="gray">
{formatChatTime(data.createdAt)}
</TextCustom>
<Grid>
<Grid.Col span={6}>
<TextCustom size="small" color="gray">
{index + 1} Agustus 2025
</TextCustom>
</Grid.Col>
<Grid.Col span={6} style={{ alignItems: "flex-end" }}>
<TextCustom size="small" color="gray">
Belum lihat
</TextCustom>
</Grid.Col>
</Grid>
</StackCustom>
</BaseBox>
</>
@@ -81,17 +95,57 @@ const BoxNotification = ({
};
export default function Notifications() {
const [activeCategory, setActiveCategory] = useState<string | null>("all");
const { user } = useAuth();
const { category } = useLocalSearchParams<{ category?: string }>();
const [activeCategory, setActiveCategory] = useState<string | null>(
category || "event"
);
const [listData, setListData] = useState<any[]>([]);
const [refreshing, setRefreshing] = useState(false);
const [loading, setLoading] = useState(false);
const handlePress = (item: any) => {
setActiveCategory(item.value);
// tambahkan logika lain seperti filter dsb.
};
useFocusEffect(
useCallback(() => {
fecthData();
}, [activeCategory])
);
const fecthData = async () => {
try {
setLoading(true);
const response = await apiGetNotificationsById({
id: user?.id as any,
category: activeCategory as any,
});
if (response.success) {
setListData(response.data);
} else {
setListData([]);
}
} catch (error) {
console.log("Error Notification", error);
} finally {
setLoading(false);
}
};
const onRefresh = () => {
setRefreshing(true);
fecthData();
setRefreshing(false);
};
return (
<ViewWrapper
<NewWrapper
headerComponent={
<ScrollableCustom
data={categories.map((e, i) => ({
data={listOfcategoriesAppNotification.map((e, i) => ({
id: i,
label: e.label,
value: e.value,
@@ -100,12 +154,21 @@ export default function Notifications() {
activeId={activeCategory as string}
/>
}
refreshControl={
<RefreshControl refreshing={refreshing} onRefresh={onRefresh} />
}
>
{Array.from({ length: 20 }).map((e, i) => (
<View key={i}>
<BoxNotification index={i} activeCategory={activeCategory as any} />
</View>
))}
</ViewWrapper>
{loading ? (
<ListSkeletonComponent />
) : _.isEmpty(listData) ? (
<NoDataText text="Belum ada notifikasi" />
) : (
listData.map((e, i) => (
<View key={i}>
<BoxNotification data={e} activeCategory={activeCategory as any} />
</View>
))
)}
</NewWrapper>
);
}

View File

@@ -31,9 +31,9 @@ import {
import pickImage from "@/utils/pickImage";
import { Ionicons } from "@expo/vector-icons";
import { Image } from "expo-image";
import { useLocalSearchParams } from "expo-router";
import { useFocusEffect, useLocalSearchParams } from "expo-router";
import _ from "lodash";
import { useEffect, useState } from "react";
import { useCallback, useEffect, useState } from "react";
import { Text, TouchableOpacity, View } from "react-native";
import PhoneInput, { ICountry } from "react-native-international-phone-number";
import { Avatar } from "react-native-paper";
@@ -76,7 +76,7 @@ export default function PortofolioCreate() {
function handleInputValue(phoneNumber: string) {
setInputValue(phoneNumber);
const callingCode = selectedCountry?.callingCode.replace(/^\+/, "") || "";
const fixNumber = inputValue.replace(/\s+/g, "");
let fixNumber = inputValue.replace(/\s+/g, "").replace(/^0+/, "");
const realNumber = callingCode + fixNumber;
setData({ ...data, tlpn: realNumber });
}
@@ -85,10 +85,12 @@ export default function PortofolioCreate() {
setSelectedCountry(country);
}
useEffect(() => {
onLoadMaster();
onLoadMasterSubBidangBisnis();
}, []);
useFocusEffect(
useCallback(() => {
onLoadMaster();
onLoadMasterSubBidangBisnis();
}, [])
);
const onLoadMaster = async () => {
try {

View File

@@ -244,7 +244,7 @@ export default function PortofolioEdit() {
const handleSubmitUpdate = async () => {
const callingCode = selectedCountry?.callingCode.replace(/^\+/, "") || "";
const fixNumber = data.tlpn.replace(/\s+/g, "");
let fixNumber = data.tlpn.replace(/\s+/g, "").replace(/^0+/, "");
const realNumber = callingCode + fixNumber;
const newData: IFormData = {

View File

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

View File

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

@@ -26,7 +26,7 @@ export default function Profile() {
const [dataToken, setDataToken] = useState<IProfile>();
const [listPortofolio, setListPortofolio] = useState<any[]>();
const { logout, isAdmin, user } = useAuth();
const { token, logout, isAdmin, user, userData } = useAuth();
const openDrawer = () => {
setIsDrawerOpen(true);
@@ -42,7 +42,8 @@ export default function Profile() {
onLoadPortofolio(id as string);
onLoadUserByToken();
isUserCheck();
}, [id])
userData(token as string);
}, [id, token])
);
const isUserCheck = () => {
@@ -63,14 +64,18 @@ export default function Profile() {
};
const onLoadPortofolio = async (id: string) => {
const response = await apiGetPortofolio({ id: id });
const lastTwoByDate = response.data
.sort(
(a: any, b: any) =>
new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()
) // urut desc
.slice(0, 2);
setListPortofolio(lastTwoByDate);
try {
const response = await apiGetPortofolio({ id: id });
const lastTwoByDate = response.data
.sort(
(a: any, b: any) =>
new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()
) // urut desc
.slice(0, 2);
setListPortofolio(lastTwoByDate);
} catch (error) {
console.log("[ERROR]", error);
}
};
return (
@@ -134,12 +139,11 @@ const ButtonnDot = ({
isUserCheck: boolean;
logout: () => Promise<void>;
}) => {
const isId = id === undefined || id === null;
console.log("[ID] >>", id);
console.log("ID CHECK", id);
const isId = id === undefined || id === "undefined";
if (isId) {
console.log("ID UNDEFINED", id);
return (
<>
<TouchableOpacity onPress={logout}>

View File

@@ -33,6 +33,16 @@ export default function ProfileLayout() {
name="create"
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>
</>
);

View File

@@ -0,0 +1,75 @@
import {
ButtonCustom,
NewWrapper,
StackCustom,
TextInputCustom,
} from "@/components";
import { useAuth } from "@/hooks/use-auth";
import { apiNotificationsSend } from "@/service/api-notifications";
import { useState } from "react";
import Toast from "react-native-toast-message";
export default function TestNotification() {
const { user } = useAuth();
const [data, setData] = useState("");
const [loading, setLoading] = useState(false);
const handleSubmit = async () => {
try {
console.log("[Data Dikirim]", data);
setLoading(true);
const response = await apiNotificationsSend({
data: {
title: "Test Notification !!",
body: data,
userLoginId: user?.id || "",
appId: "hipmi",
status: "publish",
kategoriApp: "JOB",
type: "announcement",
deepLink: "/job/cmhjz8u3h0005cfaxezyeilrr",
},
});
if (response.success) {
console.log("[RES SEND NOTIF]", JSON.stringify(response, null, 2));
Toast.show({
type: "success",
text1: "Notifikasi berhasil dikirim",
});
} else {
Toast.show({
type: "error",
text1: "Gagal mengirim notifikasi",
});
}
} catch (error) {
console.log("[ERROR SEND NOTIF]", error);
Toast.show({
type: "error",
text1: "Gagal mengirim notifikasi",
});
} finally {
setLoading(false);
}
};
return (
<>
<NewWrapper>
<StackCustom>
<TextInputCustom
required
label="Pesan"
placeholder="Masukkan pesan"
value={data}
onChangeText={(text) => setData(text)}
/>
<ButtonCustom onPress={handleSubmit} disabled={loading}>
Kirim
</ButtonCustom>
</StackCustom>
</NewWrapper>
</>
);
}

View File

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

View File

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

View File

@@ -110,7 +110,7 @@ export default function VotingCreate() {
<StackCustom gap={"xs"}>
<TextInputCustom
label="Judul Voting"
placeholder="MasukanJudul Voting"
placeholder="Masukan Judul Voting"
required
value={data.title}
onChangeText={(value: any) => setData({ ...data, title: value })}

View File

@@ -1,16 +1,17 @@
import {
AlertDefaultSystem,
BoxButtonOnFooter,
ButtonCenteredOnly,
ButtonCustom,
InformationBox,
StackCustom,
ViewWrapper,
NewWrapper,
StackCustom
} from "@/components";
import { ICON_SIZE_BUTTON } from "@/constants/constans-value";
import { useAuth } from "@/hooks/use-auth";
import { apiUser } from "@/service/api-client/api-user";
import { Ionicons } from "@expo/vector-icons";
import { router } from "expo-router";
import { RefreshControl } from "react-native";
import Toast from "react-native-toast-message";
export default function WaitingRoom() {
@@ -19,12 +20,25 @@ export default function WaitingRoom() {
async function handleCheck() {
try {
const response = await userData(token as string);
if (response.active) {
Toast.show({
type: "success",
text1: "Akun anda telah aktif", // text2: "Anda berhasil login",
});
router.replace(`/(application)/(user)/profile/create`);
const checkProfile = await apiUser(response.id);
if (checkProfile?.data?.Profile) {
Toast.show({
type: "success",
text1: "Akun anda telah aktif kembali", // text2: "Anda berhasil login",
});
router.replace(`/(application)/(user)/home`);
} else {
Toast.show({
type: "success",
text1: "Selamat ! Akun anda telah aktif", // text2: "Anda berhasil login",
});
router.replace(`/(application)/(user)/profile/create`);
}
// router.replace(`/(application)/(user)/profile/create`);
} else {
Toast.show({
type: "error",
@@ -68,10 +82,18 @@ export default function WaitingRoom() {
return (
<>
<ViewWrapper footerComponent={logoutButton()}>
<NewWrapper
footerComponent={logoutButton()}
refreshControl={
<RefreshControl refreshing={isLoading} onRefresh={handleCheck} />
}
>
<StackCustom>
<InformationBox text="Permohonan akses Anda sedang dalam proses verifikasi oleh admin. Harap tunggu, Anda akan menerima pemberitahuan melalui Whatsapp setelah disetujui." />
<ButtonCenteredOnly
<InformationBox
text="Akun Anda sedang menunggu aktivasi.
Silakan tunggu beberapa saat. Untuk memperbarui status, tarik layar ke bawah."
/>
{/* <ButtonCenteredOnly
isLoading={isLoading}
onPress={() => {
handleCheck();
@@ -79,9 +101,9 @@ export default function WaitingRoom() {
icon="refresh-ccw"
>
Check
</ButtonCenteredOnly>
</ButtonCenteredOnly> */}
</StackCustom>
</ViewWrapper>
</NewWrapper>
</>
);
}

View File

@@ -1,14 +1,28 @@
import { BackButton } from "@/components";
import BackgroundNotificationHandler from "@/components/Notification/BackgroundNotificationHandler";
import NotificationInitializer from "@/components/Notification/NotificationInitializer";
import { NotificationProvider } from "@/hooks/use-notification-store";
import { HeaderStyles } from "@/styles/header-styles";
import { Stack } from "expo-router";
export default function ApplicationLayout() {
return (
<>
<NotificationProvider>
<NotificationInitializer />
<BackgroundNotificationHandler />
<ApplicationStack />
</NotificationProvider>
</>
);
}
function ApplicationStack() {
return (
<>
<Stack screenOptions={HeaderStyles}>
<Stack.Screen name="(user)" options={{ headerShown: false }} />
<Stack.Screen name="admin" options={{ headerShown: false }} />
{/* Take Picture */}
<Stack.Screen

View File

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

View File

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

View File

@@ -1,34 +1,133 @@
/* eslint-disable react-hooks/exhaustive-deps */
import {
BoxButtonOnFooter,
ButtonCustom,
ActionIcon,
BaseBox,
CenterCustom,
LoaderCustom,
Spacing,
StackCustom,
TextInputCustom,
TextCustom,
ViewWrapper,
} from "@/components";
import { IconEdit } from "@/components/_Icon";
import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButtonAntTitle";
import { router } from "expo-router";
import { GridSpan_NewComponent } from "@/components/_ShareComponent/GridSpan_NewComponent";
import { MainColor } from "@/constants/color-palet";
import { apiAdminMasterBusinessFieldById } from "@/service/api-admin/api-master-admin";
import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
import { useCallback, useState } from "react";
import { Divider } from "react-native-paper";
export default function AdminAppInformation_BusinessFieldDetail() {
const buttonSubmit = (
<BoxButtonOnFooter>
<ButtonCustom
onPress={() => router.back()}
>
Update
</ButtonCustom>
</BoxButtonOnFooter>
const { id } = useLocalSearchParams();
const [data, setData] = useState<any | null>(null);
const [isLoading, setIsLoading] = useState(false);
useFocusEffect(
useCallback(() => {
onLoadDetail();
}, [id])
);
const onLoadDetail = async () => {
try {
const response = await apiAdminMasterBusinessFieldById({
id: id as string,
category: "all",
});
console.log("Response >>", JSON.stringify(response, null, 2));
setData(response.data);
} catch (error) {
console.log("[ERROR]", error);
setData(null);
}
};
return (
<>
<ViewWrapper footerComponent={buttonSubmit}>
<ViewWrapper>
<StackCustom>
<AdminBackButtonAntTitle title="Update Bidang Bisnis" />
<AdminBackButtonAntTitle title="Detail Bidang & Sub Bidang" />
<TextInputCustom
label="Nama Bidang Bisnis"
placeholder="Masukan Nama Bidang Bisnis"
required
/>
{!data ? (
<LoaderCustom />
) : (
<StackCustom gap={"xs"}>
<TextCustom bold>Nama Bidang</TextCustom>
<Spacing height={5} />
<BaseBox>
<StackCustom gap={"xs"}>
<TextCustom bold>
Status: {data?.bidang?.active ? "Aktif" : "Tidak Aktif"}
</TextCustom>
<GridSpan_NewComponent
span1={10}
span2={2}
text1={
<TextCustom bold size={"large"}>
{data?.bidang?.name}
</TextCustom>
}
text2={
<CenterCustom>
<ActionIcon
icon={<IconEdit size={16} color={MainColor.black} />}
onPress={() =>
router.push(
`/admin/app-information/business-field/${id}/bidang-update`
)
}
/>
</CenterCustom>
}
/>
</StackCustom>
</BaseBox>
{/* <Divider /> */}
<Spacing height={5} />
<TextCustom bold>Sub Bidang Bisnis</TextCustom>
<Spacing height={5} />
{data?.subBidang?.map((item: any, index: number) => (
<BaseBox key={index}>
<StackCustom gap={0}>
<TextCustom bold>
Status: {item?.isActive ? "Aktif" : "Tidak Aktif"}
</TextCustom>
<GridSpan_NewComponent
span1={10}
span2={2}
text1={
<TextCustom bold size={"large"}>
{item.name}
</TextCustom>
}
text2={
<CenterCustom>
<ActionIcon
icon={
<IconEdit size={16} color={MainColor.black} />
}
onPress={() =>
router.push(
`/admin/app-information/business-field/${item?.id}/sub-bidang-update`
)
}
/>
</CenterCustom>
}
/>
</StackCustom>
</BaseBox>
))}
</StackCustom>
)}
{/* <TextCustom>{JSON.stringify(data, null, 2)}</TextCustom> */}
</StackCustom>
</ViewWrapper>
</>

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