Compare commits

...

55 Commits

Author SHA1 Message Date
37d2fbe48a Fix code/
User Pages
- app/(application)/(user)/home.tsx
- app/(application)/(user)/portofolio/[id]/create.tsx

Components
- components/_ShareComponent/AppHeader.tsx

Screens
- screens/Portofolio/ScreenPortofolioCreate.tsx

Config & Dependencies
- app.config.js
- package.json
- bun.lock

iOS
- ios/HIPMIBadungConnect.xcodeproj/project.pbxproj
- ios/HIPMIBadungConnect/Info.plist

Docs
- docs/prompt-for-qwen-code.md

### No issue
2026-03-12 16:50:02 +08:00
57c9215771 Feat validasi mobile otp
modified:   context/AuthContext.tsx
        modified:   screens/Authentication/VerificationView.tsx
        modified:   service/api-config.ts

### No Issue
2026-03-11 15:04:32 +08:00
4efdbd3c7b feat: update admin features, user confirmation, and native configs
- Admin: Update layout, notification bell, and event detail screen
- User: Improve event confirmation flow
- Config: Update AndroidManifest, Info.plist, entitlements, and app.config.js

### No Issue
2026-03-11 11:29:20 +08:00
ad32eb6fe6 feat: implement deep linking & universal links for event confirmation
- Add QR code toggle for HTTPS/Custom Scheme links
- Add Universal Links (iOS) and App Links (Android) support
- Create deep link route handler with platform detection
- Add .well-known files for domain verification
- Fix Content-Type headers for apple-app-site-association
- Add environment configuration for staging & production
- Add comprehensive testing documentation

Testing:
- QR scan → Safari → App switch working 
- Platform detection working 
- Auto redirect to custom scheme working 
- Web fallback JSON response working 

### No Issue
2026-03-09 16:39:01 +08:00
a5026cc285 add: Admin Event detail screen dan komponen pendukung
Deskripsi:

Menambahkan halaman detail event pada admin panel dengan status parameter

Menambahkan beberapa komponen UI untuk menampilkan detail event, drawer informasi, dan QR Code

Update konfigurasi aplikasi dan iOS project

Perbaikan pada halaman verifikasi authentication

Update dokumentasi prompt untuk Qwen

File yang diubah:

Modified

app.config.js

app/(application)/admin/event/[id]/[status]/index.tsx

docs/prompt-for-qwen-code.md

ios/HIPMIBadungConnect.xcodeproj/project.pbxproj

ios/HIPMIBadungConnect/Info.plist

screens/Authentication/VerificationView.tsx

New Admin Event Components

screens/Admin/Event/BoxEventDetail.tsx

screens/Admin/Event/EventDetailDrawer.tsx

screens/Admin/Event/EventDetailQRCode.tsx

screens/Admin/Event/ScreenEventDetail.tsx

### No Issue
2026-03-06 16:39:55 +08:00
836ef709d2 Fix bug home
### No Issue
2026-03-05 16:41:29 +08:00
3bbee15c3a Perbaikan Bug & Error Handling: │
│                                                                                                            │
  │    1. Device Token Registration Error (HTTP 500)                                                           │
  │       - File: service/api-device-token.ts                                                                  │
  │       - Fix: Hapus nested data wrapper pada payload                                                        │
  │       - Improvement: Tambahkan error logging detail                                                        │
  │                                                                                                            │
  │    2. Uncaught Promise Errors                                                                              │
  │       - File: components/Notification/NotificationInitializer.tsx                                          │
  │       - Fix: Better error handling untuk device token registration                                         │
  │       - File: app/(application)/(user)/home.tsx                                                            │
  │       - Fix: Add .catch() untuk userData() dan error handling apiUser()                                    │
  │       - File: app/(application)/(user)/profile/[id]/index.tsx                                              │
  │       - Fix: Add error handling untuk apiProfile(), apiUser(), userData()                                  │
  │                                                                                                            │
  │    3. UI Improvements                                                                                      │
  │       - File: app/(application)/(user)/home.tsx                                                            │
  │       - Feature: 4 skeleton lingkaran untuk loading state grid features                                    │
  │                                                                                                            │
  │    4. Maps Migration                                                                                       │
  │       - File: app/(application)/admin/maps.tsx                                                             │
  │       - Change: Replace react-native-maps dengan MapsV2Custom (Maplibre)                                   │
  │       - Cleanup: Hapus unused imports dan interfaces                                                       │
  │                                                                                                            │
  │   Files Modified (7)                                                                                       │
  │    - app/(application)/(user)/home.tsx                                                                     │
  │    - app/(application)/(user)/profile/[id]/index.tsx                                                       │
  │    - app/(application)/admin/maps.tsx                                                                      │
  │    - components/Notification/NotificationInitializer.tsx                                                   │
  │    - service/api-device-token.ts                                                                           │
  │    - constants/constans-value.ts                                                                           │
  │    - screens/Home/bottomFeatureSection.tsx                                                                 │
  │    - screens/UserSeach/MainView_V2.tsx

### No Issue
2026-03-04 16:39:57 +08:00
ad7dbaf162 Fix Bug DB
User Pages
- app/(application)/(user)/home.tsx
- app/(application)/(user)/portofolio/[id]/index.tsx
- app/(application)/(user)/profile/[id]/index.tsx

Home
- screens/Home/bottomFeatureSection.tsx

Components
- components/Notification/NotificationInitializer.tsx
- components/_ShareComponent/SkeletonCustom.tsx

Service
- service/api-device-token.ts

Config & iOS
- app.config.js
- ios/HIPMIBadungConnect.xcodeproj/project.pbxproj
- ios/HIPMIBadungConnect/Info.plist

### No Issue
2026-03-03 16:44:45 +08:00
9c94ec0454 Fix Bug
Maps Platform Update
- components/Map/MapSelectedPlatform.tsx
- components/Map/MapSelectedV2.tsx

Maps Screens
- screens/Maps/ScreenMapsCreate.tsx
- screens/Maps/ScreenMapsEdit.tsx

Home
- screens/Home/bottomFeatureSection.tsx

Config & Native
- app.config.js
- android/app/build.gradle
- ios/HIPMIBadungConnect.xcodeproj/project.pbxproj
- ios/HIPMIBadungConnect/Info.plist

### No Issue
2026-03-02 16:34:24 +08:00
4c63485a5b Clean Code Edit Maps
Maps Edit Feature
- app/(application)/(user)/maps/[id]/edit.tsx
- components/Map/MapSelectedV2.tsx

Docs
- docs/prompt-for-qwen-code.md

New Screen
- screens/Maps/ScreenMapsEdit.tsx

### No Issue
2026-03-02 10:31:29 +08:00
f5d09a2906 Fix create maps
iOS Project
- HIPMIBadungConnect.xcodeproj/project.pbxproj

Maps & Location Screens
- screens/Maps/MapsView2.tsx
- screens/Portofolio/BusinessLocationSection.tsx

New Map Components
- components/Map/MapsV2Custom.tsx
- components/Map/SelectLocationMap.tsx

### No Issue
2026-02-27 16:57:01 +08:00
67070bb2f1 Fix Maps
iOS Project
- HIPMIBadungConnect.xcodeproj/project.pbxproj

Maps & Location Screens
- screens/Maps/MapsView2.tsx
- screens/Portofolio/BusinessLocationSection.tsx

New Map Components
- components/Map/MapsV2Custom.tsx
- components/Map/SelectLocationMap.tsx

### No Issue
2026-02-26 18:04:45 +08:00
fb19ec60b2 Fix Maps
iOS Project
- HIPMIBadungConnect.xcodeproj/project.pbxproj

Maps & Location Screens
- screens/Maps/MapsView2.tsx
- screens/Portofolio/BusinessLocationSection.tsx

New Map Components
- components/Map/MapsV2Custom.tsx
- components/Map/SelectLocationMap.tsx

### No Issue
2026-02-26 17:53:23 +08:00
e8f5c5b174 User Maps
- app/(application)/(user)/maps/index.tsx
- screens/Maps/MapsView2.tsx

New Maps Component
- screens/Maps/DrawerMaps.tsx

Docs / Backup
- docs/PODS.back

### No Issue
2026-02-25 16:46:37 +08:00
74a4d88277 Fix POD File & Maps
User Maps
- app/(application)/(user)/maps/index.tsx
- screens/Maps/MapsView2.tsx

iOS
- HIPMIBadungConnect.xcodeproj/project.pbxproj
- Podfile.lock
- HIPMIBadungConnect.xcworkspace/xcshareddata/

New Maps Component
- screens/Maps/DrawerMaps.tsx

Docs / Backup
- docs/PODS.back

### No Issue
2026-02-25 16:37:42 +08:00
2ad93a26a8 Tampilan maps android
- screens/Maps/MapsView2.tsx

### No Issue
2026-02-24 17:57:14 +08:00
768b0caa9e Fix maps
### No Issue
2026-02-24 16:48:37 +08:00
208b0ce813 Fix Maps
Config & Dependencies
- app.config.js
- package.json
- bun.lock
- ios/Podfile
- ios/HIPMIBadungConnect.xcodeproj/project.pbxproj

User Pages
- app/(application)/(user)/maps/index.tsx
- app/(application)/(user)/portofolio/[id]/index.tsx

Maps
- screens/Maps/MapsView2.tsx

### No Issue
2026-02-24 16:41:43 +08:00
66e6aebf41 Fixed Admin Package
Config & Dependencies
- app.config.js
- package.json
- bun.lock
- ios/Podfile
- ios/HIPMIBadungConnect.xcodeproj/project.pbxproj

Home
- screens/Home/tabsList.ts

Maps
- screens/Maps/MapsView2.tsx

### No Issue
2026-02-24 07:18:01 +08:00
32a42d1b60 Fix UI Admin
User & Image
- app/(application)/(image)/take-picture/[id]/index.tsx
- app/(application)/(user)/home.tsx

Admin – Forum
- app/(application)/admin/forum/[id]/index.tsx
- app/(application)/admin/forum/[id]/list-comment.tsx
- app/(application)/admin/forum/[id]/list-report-comment.tsx

Admin Screens – Forum
- screens/Admin/Forum/ScreenForumDetailReportPosting.tsx
- screens/Admin/Forum/ScreenForumReportComment.tsx
- screens/Admin/Forum/ScreenForumReportPosting.tsx

New Admin Screens – Forum
- screens/Admin/Forum/ScreenForumDetailReportComment.tsx
- screens/Admin/Forum/ScreenForumListComment.tsx

Home
- screens/Home/bottomFeatureSection.tsx

Service
- service/api-admin/api-admin-forum.ts

Docs
- docs/prompt-for-qwen-code.md

### No Issue
2026-02-20 16:48:26 +08:00
107d4312e1 Fixed UI Admin
Admin Forum Pages
- app/(application)/admin/forum/[id]/list-report-posting.tsx
- app/(application)/admin/forum/posting.tsx
- app/(application)/admin/forum/report-comment.tsx
- app/(application)/admin/forum/report-posting.tsx

Admin Forum Service
- service/api-admin/api-admin-forum.ts

Docs
- docs/prompt-for-qwen-code.md

New Admin Screens
- screens/Admin/Forum/

### No Issue
2026-02-19 16:40:35 +08:00
4862975402 Ringkasan Perubahan
File yang Dimodifikasi:
     1. `service/api-admin/api-admin-investment.ts` - Tambah parameter page
        untuk pagination
     2. `app/(application)/admin/investment/[id]/list-of-investor.tsx` - Clean
         route file

    File Baru:
     3. `screens/Admin/Investment/ScreenInvestmentListOfInvestor.tsx` - Screen
         component dengan pagination
     4. `screens/Admin/Investment/BoxInvestmentListOfInvestor.tsx` - Box
        component untuk list item

### No Issue
2026-02-19 14:06:02 +08:00
f284e2ec02 delete comment 2026-02-18 17:20:28 +08:00
1d61ad51e5 Fixed Admin UI
Admin – Donation Pages
- app/(application)/admin/donation/[id]/detail-disbursement-of-funds.tsx
- app/(application)/admin/donation/[id]/list-disbursement-of-funds.tsx
- app/(application)/admin/donation/[id]/list-of-donatur.tsx
- app/(application)/admin/donation/[status]/status.tsx
- app/(application)/admin/donation/category-update.tsx
- app/(application)/admin/donation/category.tsx

Admin Services
- service/api-admin/api-admin-donation.ts
- service/api-admin/api-master-admin.ts

Admin Screens (Updated)
- screens/Admin/Voting/ScreenEventTypeOfEvent.tsx

Docs
- docs/prompt-for-qwen-code.md

New Donation Components
- screens/Admin/Donation/BoxDonationCategory.tsx
- screens/Admin/Donation/BoxDonationListDisbursementOfFunds.tsx
- screens/Admin/Donation/BoxDonationListOfDonatur.tsx
- screens/Admin/Donation/BoxDonationStatus.tsx

New Donation Screens
- screens/Admin/Donation/ScreenDonationCategory.tsx
- screens/Admin/Donation/ScreenDonationListDisbursementOfFunds.tsx
- screens/Admin/Donation/ScreenDonationListOfDonatur.tsx
- screens/Admin/Donation/ScreenDonationStatus.tsx

### No Issue"
2026-02-18 17:19:46 +08:00
76845b71b4 ## Perubahan Tampilan Admin
### File Baru (4)
- `screens/Admin/Voting/ScreenVotingStatus.tsx`
- `screens/Admin/Voting/ScreenVotingHistory.tsx`
- `screens/Admin/Voting/ScreenEventTypeOfEvent.tsx`
- `screens/Admin/Voting/BoxVotingStatus.tsx`

### File Diubah (3)
- `app/(application)/admin/voting/[status]/status.tsx` → 5 baris
- `app/(application)/admin/voting/history.tsx` → 5 baris
- `app/(application)/admin/event/type-of-event.tsx` → 5 baris

### API Updates (2)
- `service/api-admin/api-admin-voting.ts` → tambah param `page`
- `service/api-admin/api-master-admin.ts` → tambah param `page`

## Fitur Baru
- Pagination (infinite scroll)
- Pull-to-Refresh
- Skeleton Loading
- Empty State
- Search Functionality

### No Issue"
2026-02-18 14:28:15 +08:00
97e1f50660 Ringkasan Perubahan
Kami telah melakukan serangkaian perubahan pada file app/(application)/admin/event/[id]/[status]/index.tsx
    untuk memperbaiki error dan meningkatkan fungsionalitas aplikasi. Berikut adalah perubahan-perubahan yang
    telah dilakukan:

    1. Perbaikan Fungsi Download QR Code
     - Mengganti implementasi fungsi downloadQRCode yang sebelumnya menggunakan modul native
       (react-native-view-shot dan @react-native-camera-roll/camera-roll) yang menyebabkan error
     - Mengganti dengan implementasi sederhana yang menampilkan pesan bahwa fitur sedang dalam pengembangan
     - Menambahkan pengecekan platform untuk memastikan fitur hanya berjalan di platform yang didukung (non-web)

    2. Pembersihan Kode
     - Menghapus penggunaan useRef karena tidak lagi diperlukan setelah mengganti implementasi
     - Menghapus komponen View yang digunakan sebagai referensi karena tidak lagi diperlukan
     - Menyederhanakan struktur komponen QR code

    3. Perbaikan Tampilan
     - Menyesuaikan tampilan tombol download agar tetap muncul meskipun QR code sedang dimuat
     - Memastikan bahwa tombol download QR tetap terlihat dan fungsional

    4. Penanganan Error
     - Menambahkan penanganan error yang lebih baik untuk mencegah crash aplikasi
     - Mengganti implementasi yang menyebabkan error Invariant Violation terkait modul native yang tidak terdaftar

    Tujuan dari Perubahan Ini

     1. Mengatasi Error Runtime: Mengatasi error Invariant Violation: TurboModuleRegistry.getEnforcing(...):
        'RNViewShot' could not be found dan error terkait modul native lainnya
     2. Meningkatkan Stabilitas Aplikasi: Memastikan bahwa aplikasi tidak mengalami crash akibat modul yang tidak
        terdaftar
     3. Menyederhanakan Fungsionalitas: Menyediakan implementasi sementara untuk fitur download QR code sampai
        konfigurasi native module selesai
     4. Meningkatkan Pengalaman Pengguna: Memastikan bahwa antarmuka tetap responsif dan memberikan umpan balik yang
         jelas kepada pengguna

    File yang Terpengaruh

     - app/(application)/admin/event/[id]/[status]/index.tsx

### No Issue
2026-02-14 15:33:42 +08:00
1cbe4ab330 Ringkasan Perubahan
1. Penambahan Pagination pada Fitur Admin Job
     - Menerapkan sistem pagination menggunakan hook usePagination dari hooks/use-pagination.tsx
     - Mengintegrasikan komponen-komponen pagination dari helpers/paginationHelpers.tsx
     - Menambahkan dukungan infinite scroll dan pull-to-refresh
     - Menambahkan loading state, skeleton loader, dan empty state

    2. Pembaruan Fungsi API
     - Memperbarui fungsi apiAdminJob di service/api-admin/api-admin-job.ts untuk mendukung parameter
       pagination
     - Menambahkan parameter page dengan nilai default 1

    3. Modularisasi Kode
     - Memindahkan komponen AdminJobStatus dari app/(application)/admin/job/[status]/status.tsx ke
       screens/Admin/Job/ScreenJobStatus.tsx
     - Mengganti ViewWrapper dengan NewWrapper untuk tampilan yang lebih fleksibel
     - Membuat komponen baru BoxStatusJob.tsx untuk memisahkan logika tampilan item pekerjaan
     - Menggunakan komponen BoxStatusJob di dalam ScreenJobStatus untuk menampilkan daftar pekerjaan

    4. Perbaikan Struktur dan Organisasi Kode
     - Mengorganisir ulang struktur folder untuk komponen admin job
     - Memisahkan tanggung jawab antara komponen layar dan komponen item
     - Mengoptimalkan performa dengan menggunakan useCallback dan useMemo

    File-file yang Diubah

     1. screens/Admin/Job/ScreenJobStatus.tsx - Implementasi utama dengan pagination
     2. screens/Admin/Job/BoxStatusJob.tsx - Komponen baru untuk menampilkan item pekerjaan
     3. service/api-admin/api-admin-job.ts - Penambahan parameter pagination
     4. app/(application)/admin/job/[status]/status.tsx - Diperbarui untuk menggunakan komponen baru

### NO Issue
2026-02-14 11:57:27 +08:00
42fa80c228 Fix Admin
Admin – App Information
- app/(application)/admin/app-information/index.tsx
- app/(application)/admin/app-information/business-field/[id]/index.tsx

Admin Screens
- screens/Admin/App-Information/BusinessFieldSection.tsx
- screens/Admin/App-Information/InformationBankSection.tsx
- screens/Admin/User-Access/ScreenUserAccess.tsx

New Admin Screens
- screens/Admin/App-Information/ScreenAppInformation.tsx
- screens/Admin/App-Information/ScreenBusinessFieldDetail.tsx

Shared Components
- components/_ShareComponent/Admin/BoxTitlePage.tsx

API Service
- service/api-admin/api-master-admin.ts

Styles
- styles/global-styles.ts

Docs
- docs/prompt-for-qwen-code.md

### No Issue
2026-02-13 17:38:48 +08:00
fb697366fe Fixed admin user access
Admin Layout & Pages
- app/(application)/admin/_layout.tsx
- app/(application)/admin/user-access/index.tsx

Admin Components
- components/Drawer/NavbarMenu.tsx
- components/_ShareComponent/Admin/BoxTitlePage.tsx
- components/_ShareComponent/Admin/AdminBasicBox.tsx

Admin Screens
- screens/Admin/User-Access/

API – Admin User Access
- service/api-admin/api-admin-user-access.ts

Docs
- docs/prompt-for-qwen-code.md

### No issue
2026-02-12 17:35:28 +08:00
6d71c3a86f Navbar menu versi 3
Admin Layout
- app/(application)/admin/_layout.tsx

Docs
- docs/prompt-for-qwen-code.md

New Component
- components/Drawer/NavbarMenu_V3.tsx

### No Issue'
2026-02-12 14:55:05 +08:00
e030b8f486 Fixed navbar admin
User Layout
- app/(application)/(user)/home.tsx

Components
- components/Drawer/NavbarMenu_V2.tsx

Docs
- docs/admin-folder-structure.md

### Issue: saat masuk lebih dalam ke sub menu indikator aktif di navbar hilang
2026-02-12 11:48:01 +08:00
5c931b069c Fixed navbar admin
User & Admin Layout
- app/(application)/(user)/home.tsx
- app/(application)/admin/_layout.tsx

Components
- components/Drawer/NavbarMenu.tsx
- components/index.ts

Docs
- docs/prompt-for-qwen-code.md

Backup Component
- components/Drawer/NavbarMenu.back.tsx

New Components
- components/Drawer/NavbarMenu_V2.tsx
- components/_ShareComponent/BasicWrapper.tsx

New Admin Screen
- screens/Admin/listPageAdmin_V2.tsx

### No Issue
2026-02-11 17:40:08 +08:00
b2be7be533 Saya telah melakukan serangkaian perubahan penting dalam pengembangan aplikasi HIPMI Mobile, khususnya dalam modul
Donasi. Berikut adalah ringkasan perubahan yang telah dilakukan:

     1. Menerapkan sistem pagination pada berbagai komponen layar donasi:
        - ScreenBeranda.tsx
        - ScreenMyDonation.tsx
        - ScreenRecapOfNews.tsx
        - ScreenListOfNews.tsx
        - ScreenListOfDonatur.tsx
        - ScreenFundDisbursement.tsx

     2. Memperbarui fungsi-fungsi API untuk mendukung parameter page:
        - apiDonationGetAll
        - apiDonationGetNewsById
        - apiDonationListOfDonaturById
        - apiDonationDisbursementOfFundsListById

     3. Mengganti komponen wrapper lama (ViewWrapper) dengan NewWrapper yang mendukung sistem pagination

     4. Membuat komponen layar terpisah untuk meningkatkan modularitas kode

     5. Memperbaiki berbagai error yang terjadi, termasuk masalah dengan import komponen dan struktur JSX

### No Issue
2026-02-10 17:30:30 +08:00
2705f96b01 feat: implement pagination and NewWrapper on donation and investment screens
- Implement pagination on investment screens (ScreenMyHolding, ScreenInvestor, ScreenRecapOfNews, ScreenListOfNews)
- Implement pagination on donation screens (ScreenStatus)
- Update API functions to support pagination with page parameter (apiInvestmentGetAll, apiInvestmentGetInvestorById, apiInvestmentGetNews, apiDonationGetByStatus)
- Replace ViewWrapper with NewWrapper for better UI experience
- Update app directory files to use new modular components from screens directory
- Add pull-to-refresh and infinite scroll functionality
- Improve performance by loading data incrementally
- Apply NewWrapper to donation create and create-story screens

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-02-09 17:35:54 +08:00
38a6b424e8 Ringkasan Perubahan
1. Pembuatan dan Pembaruan Komponen Layar Investasi dengan Sistem Pagination

ScreenMyHolding.tsx
 - Diperbarui dari sistem loading data statis ke sistem pagination dinamis
 - Menggunakan hook usePagination untuk manajemen data
 - Mengganti ViewWrapper dengan NewWrapper untuk tampilan yang lebih modern
 - Menambahkan fitur pull-to-refresh dan infinite scroll
 - Menggunakan helper pagination dari createPaginationComponents

ScreenInvestor.tsx
 - Dibuat baru dengan sistem pagination
 - Menggunakan hook usePagination untuk manajemen data
 - Menggunakan NewWrapper sebagai komponen dasar
 - Menambahkan fitur pull-to-refresh dan infinite scroll

ScreenRecapOfNews.tsx
 - Dibuat baru dengan sistem pagination
 - Menggunakan hook usePagination untuk manajemen data
 - Menggunakan NewWrapper sebagai komponen dasar
 - Menambahkan fitur pull-to-refresh dan infinite scroll

ScreenListOfNews.tsx
 - Dibuat baru dengan sistem pagination
 - Menggunakan hook usePagination untuk manajemen data
 - Menggunakan NewWrapper sebagai komponen dasar
 - Menambahkan fitur pull-to-refresh dan infinite scroll

2. Perubahan pada Fungsi-Fungsi API untuk Mendukung Pagination

apiInvestmentGetAll
 - Sudah mendukung parameter page dengan default "1"

apiInvestmentGetInvestorById
 - Ditambahkan parameter page dengan default "1"
 - Memungkinkan pengambilan data investor secara bertahap

apiInvestmentGetNews
 - Ditambahkan parameter page dengan default "1"
 - Memungkinkan pengambilan data berita secara bertahap

3. Pembaruan File-File di Direktori app/ untuk Menggunakan Komponen-Komponen Baru

app/(application)/(user)/investment/[id]/investor.tsx
 - Diperbarui untuk menggunakan komponen Investment_ScreenInvestor
 - Menghilangkan logika lokal dan menggantinya dengan komponen modular

app/(application)/(user)/investment/[id]/(news)/recap-of-news.tsx
 - Diperbarui untuk menggunakan komponen Investment_ScreenRecapOfNews
 - Menghilangkan logika lokal dan menggantinya dengan komponen modular

app/(application)/(user)/investment/[id]/(news)/list-of-news.tsx
 - Diperbarui untuk menggunakan komponen Investment_ScreenListOfNews
 - Menghilangkan logika lokal dan menggantinya dengan komponen modular

4. Implementasi Sistem Pagination

usePagination Hook
 - Digunakan secara konsisten di semua komponen layar investasi
 - Menyediakan fitur load more dan refresh
 - Mengelola state loading, refreshing, dan data

NewWrapper Component
 - Digunakan sebagai pengganti ViewWrapper
 - Menyediakan dukungan bawaan untuk FlatList
 - Menyediakan dukungan untuk refreshControl dan onEndReached

Pagination Helpers
 - Menggunakan createPaginationComponents untuk menghasilkan komponen-komponen pagination
 - Menyediakan skeleton loading saat data sedang dimuat
 - Menyediakan pesan kosong saat tidak ada data

5. Manfaat dari Perubahan Ini

 1. Performa Lebih Baik: Dengan pagination, hanya sejumlah kecil data yang dimuat pada satu waktu, meningkatkan
    performa aplikasi
 2. Pengalaman Pengguna Lebih Baik: Fitur pull-to-refresh dan infinite scroll membuat navigasi lebih intuitif
 3. Kode Lebih Modular: Komponen-komponen baru dapat digunakan kembali dan dipelihara lebih mudah
 4. Struktur Kode Lebih Rapi: Pemisahan antara logika tampilan dan logika data membuat kode lebih terorganisir

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-02-09 14:42:56 +08:00
83fa277e03 Fix Loaddata Invesment & Clearing code
UI – Investment (User)
- app/(application)/(user)/investment/(tabs)/index.tsx
- app/(application)/(user)/investment/(tabs)/portofolio.tsx
- app/(application)/(user)/investment/(tabs)/transaction.tsx
- app/(application)/(user)/investment/[id]/(document)/list-of-document.tsx
- app/(application)/(user)/investment/[id]/(document)/recap-of-document.tsx
- app/(application)/(user)/investment/[id]/(transaction-flow)/invoice.tsx
- app/(application)/(user)/investment/[id]/(transaction-flow)/select-bank.tsx

Screens – Investment
- screens/Invesment/ButtonStatusSection.tsx
- screens/Invesment/Document/RecapBoxDetail.tsx
- screens/Invesment/Document/ScreenListDocument.tsx
- screens/Invesment/Document/ScreenRecap.tsx
- screens/Invesment/ScreenBursa.tsx
- screens/Invesment/ScreenPortofolio.tsx
- screens/Invesment/ScreenTransaction.tsx

Profile
- app/(application)/(user)/profile/[id]/detail-blocked.tsx

API Client
- service/api-client/api-investment.ts

Docs
- docs/prompt-for-qwen-code.md

### No issue
2026-02-06 17:27:12 +08:00
c570a19d84 Fix Wrapper
UI – Investment (User)
- app/(application)/(user)/investment/create.tsx
- app/(application)/(user)/investment/[id]/edit.tsx

### No Issue"
2026-02-05 17:30:17 +08:00
7415c8c8ce Fix path Notification admin
UI – Notifications (User)
- app/(application)/(user)/notifications/index.tsx
- screens/Notification/ScreenNotification_V2.tsx

UI – Notifications (Admin)
- screens/Admin/Notification-Admin/ScreenNotificationAdmin2.tsx

API
- service/api-notifications.ts

Docs
- docs/prompt-for-qwen-code.md

### No Issue
2026-02-05 16:37:50 +08:00
72a3d42013 Fix Loaddata Voting
UI – Voting (User)
- app/(application)/(user)/voting/(tabs)/index.tsx
- app/(application)/(user)/voting/(tabs)/history.tsx
- app/(application)/(user)/voting/(tabs)/contribution.tsx
- app/(application)/(user)/voting/create.tsx
- app/(application)/(user)/voting/[id]/edit.tsx
- app/(application)/(user)/voting/[id]/[status]/detail.tsx
- app/(application)/(user)/voting/[id]/list-of-contributor.tsx

UI – Event
- app/(application)/(user)/event/[id]/edit.tsx
- screens/Event/ScreenStatus.tsx

UI – Voting Screens (New)
- screens/Voting/ScreenBeranda.tsx
- screens/Voting/ScreenContribution.tsx
- screens/Voting/ScreenHistory.tsx
- screens/Voting/ScreenListOfContributor.tsx
- screens/Voting/ScreenStatus.tsx
- screens/Voting/ButtonStatusSection.tsx

UI – Job
- screens/Job/ButtonStatusSection.tsx
- screens/Job/MainViewStatus2.tsx

API Client
- service/api-client/api-voting.ts

Docs
- docs/prompt-for-qwen-code.md

### No Issue
2026-02-05 15:06:14 +08:00
d0abd14047 Fix Loaddata Voting
Voting – User
- app/(application)/(user)/voting/(tabs)/status.tsx
- app/(application)/(user)/voting/create.tsx

Screens – Voting
- screens/Voting/ButtonStatusSection.tsx

API Client
- service/api-client/api-voting.ts

Global
- app/+not-found.tsx
- styles/global-styles.ts

Docs
- docs/prompt-for-qwen-code.md

Untracked (New Files)
- screens/Voting/ScreenStatus.tsx

### No issue
2026-02-04 17:44:57 +08:00
5b2be20469 Fix Loaddata pada event dan perbaikan tampilan pada NewWrapper
Event – User
- app/(application)/(user)/event/(tabs)/contribution.tsx
- app/(application)/(user)/event/(tabs)/index.tsx
- app/(application)/(user)/event/[id]/list-of-participants.tsx

Voting – User
- app/(application)/(user)/voting/(tabs)/history.tsx

Components
- components/Notification/NotificationInitializer.tsx
- components/_ShareComponent/NewWrapper.tsx

Screens – Event
- screens/Event/BoxPublishSection.tsx
- screens/Event/ButtonStatusSection.tsx
- screens/Event/ScreenHistory.tsx
- screens/Event/ScreenStatus.tsx

Screens – Forum
- screens/Forum/ViewBeranda3.tsx

API Client
- service/api-client/api-event.ts

Styles
- styles/global-styles.ts

Docs
- docs/prompt-for-qwen-code.md

Untracked (New Files)
- screens/Event/ScreenBeranda.tsx
- screens/Event/ScreenContribution.tsx
- screens/Event/ScreenListOfParticipants.tsx

#### No Issue
2026-02-04 16:56:48 +08:00
60177a1087 Fix Component Datetime IOS
Components
- components/DateInput/DataTimeAndroid.tsx
- components/DateInput/DateTimeIOS.tsx
- components/Notification/NotificationInitializer.tsx

Screens
- screens/Event/ScreenStatus.tsx

Docs
- docs/prompt-for-qwen-code.md

### No Issue
2026-02-04 12:00:00 +08:00
771ae45f26 Fix load data event
Event – User
- app/(application)/(user)/event/(tabs)/history.tsx
- app/(application)/(user)/event/(tabs)/status.tsx
- app/(application)/(user)/event/[id]/edit.tsx
- app/(application)/(user)/event/create.tsx

Event – Screens (Untracked)
- screens/Event/ScreenHistory.tsx
- screens/Event/ScreenStatus.tsx

API
- service/api-client/api-event.ts

Docs
- docs/prompt-for-qwen-code.md

### No Issue
2026-02-03 17:45:27 +08:00
41f4a8ac99 Fix load data notification
Notification – User
- app/(application)/(user)/notifications/index.tsx
- screens/Notification/ScreenNotification_V1.tsx
- screens/Notification/ScreenNotification_V2.tsx

Notification – Admin
- app/(application)/admin/notification/index.tsx
- screens/Admin/Notification-Admin/

Job
- screens/Job/MainViewStatus2.tsx

Docs
- docs/prompt-for-qwen-code.md

Deleted
- screens/Notification/ScreenNotification.tsx

### No Issue
2026-02-03 16:59:09 +08:00
48196cd46b Fix Load data pada halaman yang membutuhkan infinite load
Job – User App
- app/(application)/(user)/job/(tabs)/index.tsx
- app/(application)/(user)/job/(tabs)/status.tsx
- app/(application)/(user)/job/(tabs)/archive.tsx
- app/(application)/(user)/job/create.tsx

Job – Screens
- screens/Job/ScreenBeranda.tsx
- screens/Job/ScreenBeranda2.tsx
- screens/Job/MainViewStatus.tsx
- screens/Job/MainViewStatus2.tsx
- screens/Job/ScreenArchive.tsx
- screens/Job/ScreenArchive2.tsx

API – Job (Client)
- service/api-client/api-job.ts

Notification
- screens/Notification/ScreenNotification.tsx

Docs
- QWEN.md
- docs/prompt-for-qwen-code.md

### No Issue
2026-02-02 17:09:58 +08:00
ec79a1fbcd Fix semua tampilan yang memiliki fungsi infitine load
UI – User Notifications
- app/(application)/(user)/notifications/index.tsx
- service/api-notifications.ts
- screens/Notification/

UI – Portofolio (User)
- app/(application)/(user)/portofolio/[id]/create.tsx
- app/(application)/(user)/portofolio/[id]/edit.tsx
- app/(application)/(user)/portofolio/[id]/list.tsx
- screens/Portofolio/BoxPortofolioView.tsx
- screens/Portofolio/ViewListPortofolio.tsx
- screens/Profile/PortofolioSection.tsx
- service/api-client/api-portofolio.ts

Forum & User Search
- screens/Forum/DetailForum2.tsx
- screens/Forum/ViewBeranda3.tsx
- screens/UserSeach/MainView_V2.tsx

Constants & Docs
- constants/constans-value.ts
- docs/prompt-for-qwen-code.md

### No Issue
2026-01-30 17:18:47 +08:00
ed16f1b204 Fix forum detail
Forum (User)
- app/(application)/(user)/forum/[id]/index.tsx
- screens/Forum/ViewForumku2.tsx
- service/api-client/api-forum.ts

Forum – New / Refactor
- screens/Forum/DetailForum.tsx
- screens/Forum/DetailForum2.tsx

Documentation
- docs/

Removed
- hipmi-note.md

### No Issue'
2026-01-29 17:36:17 +08:00
d693550a1f Fix Alur Login & Load data forum , user search
Admin – User Access
- app/(application)/admin/user-access/[id]/index.tsx

Authentication
- context/AuthContext.tsx
- screens/Authentication/EULASection.tsx
- screens/Authentication/LoginView.tsx

Forum
- screens/Forum/ViewBeranda3.tsx

Profile & UI Components
- components/Image/AvatarComp.tsx
- screens/Profile/AvatarAndBackground.tsx

### No Issue
2026-01-29 15:08:00 +08:00
b3bfbc0f7e Fix Infinite Load Data
Forum & User Search – User
- app/(application)/(user)/forum/index.tsx
- app/(application)/(user)/user-search/index.tsx

Global & Core
- app/+not-found.tsx
- screens/RootLayout/AppRoot.tsx
- constants/constans-value.ts

Component
- components/Image/AvatarComp.tsx

API Client
- service/api-client/api-user.ts

Untracked Files
- QWEN.md
- helpers/
- hooks/use-pagination.tsx
- screens/Forum/ViewBeranda3.tsx
- screens/UserSeach/

### No Issue
2026-01-29 11:36:24 +08:00
71e45d06cc Donation – User
- app/(application)/(user)/donation/(tabs)/index.tsx
- app/(application)/(user)/donation/(tabs)/my-donation.tsx
- app/(application)/(user)/donation/[id]/(transaction-flow)/index.tsx

Donation – Admin
- app/(application)/admin/donation/[id]/disbursement-of-funds.tsx

Image Preview
- app/(application)/(image)/preview-image/[id]/index.tsx

### No Issue
2026-01-27 17:42:14 +08:00
07e64c335e Donation – App & Admin
- app/(application)/(user)/donation/(tabs)/status.tsx
- app/(application)/(user)/donation/create-story.tsx
- app/(application)/admin/donation/[id]/[status]/transaction-detail.tsx
- app/(application)/admin/donation/[id]/reject-input.tsx
- screens/Admin/Donation/funDonationUpdateStatus.ts
- service/api-admin/api-admin-donation.ts

Config
- app.config.js
- ios/HIPMIBadungConnect/Info.plist

### No Issue
2026-01-23 17:13:06 +08:00
1aebc9b4e8 Logika EULA
Dipindah ke halaman login dan cek dengan modal

Fix:
Authentication & EULA
- context/AuthContext.tsx
- screens/Authentication/EULAView.tsx
- screens/Authentication/LoginView.tsx

Add:
- components/Modal/ModalReactNative.tsx
- screens/Authentication/EULASection.tsx

### No Issue
2026-01-23 14:45:44 +08:00
5665dc88ba Notification investasi done
### No Isssue
2026-01-22 17:54:33 +08:00
da82a02a45 Investment UI (Admin & User)
- app/(application)/(user)/investment/[id]/(transaction-flow)/invoice.tsx
- app/(application)/admin/investment/[id]/[status]/index.tsx
- app/(application)/admin/investment/[id]/[status]/transaction-detail.tsx
- app/(application)/admin/investment/[status]/status.tsx

API Service (Admin)
- service/api-admin/api-admin-investment.ts

### No issue
2026-01-21 15:39:08 +08:00
14c0f0e499 User – Investment
app/(application)/(user)/investment/(tabs)/_layout.tsx

app/(application)/(user)/investment/(tabs)/portofolio.tsx

app/(application)/(user)/investment/create.tsx

Admin – Investment

app/(application)/admin/investment/[id]/reject-input.tsx

Screens / UI

screens/Invesment/BoxBerandaSection.tsx

API

service/api-admin/api-admin-investment.ts

Utils

utils/pickFile.ts

### No Issue
2026-01-20 17:41:02 +08:00
291 changed files with 20536 additions and 10559 deletions

515
QWEN.md Normal file
View File

@@ -0,0 +1,515 @@
# HIPMI Mobile Application - Development Context
## Project Overview
HIPMI Mobile is a cross-platform mobile application built with Expo and React Native. The application is named **"HIPMI Badung Connect"** and serves as a platform for the HIPMI (Himpunan Pengusaha dan Pengusaha Indonesia) Badung chapter. It's designed to run on iOS, Android, and web platforms using a single codebase.
### Key Technologies
- **Framework**: Expo (v54.0.0) with React Native (v0.81.5)
- **Language**: TypeScript
- **Architecture**: File-based routing with Expo Router
- **State Management**: Context API (AuthContext)
- **UI Components**: React Native Paper, custom components
- **Maps Integration**: Maplibre Maps for React Native (`@maplibre/maplibre-react-native` v10.4.2)
- **Push Notifications**: React Native Firebase Messaging
- **Build System**: Metro bundler
- **Package Manager**: Bun
### Project Structure
```
hipmi-mobile/
├── app/ # Main application screens and routing (Expo Router)
│ ├── _layout.tsx # Root layout component
│ ├── index.tsx # Entry point (Login screen)
│ └── (application)/ # Main app screens
│ ├── admin/ # Admin panel screens
│ ├── (user)/ # User screens
│ └── ...
├── components/ # Reusable UI components
│ ├── _ShareComponent/ # Shared components (NewWrapper, Admin components)
│ ├── _Icon/ # Icon components
│ └── ...
├── context/ # State management (AuthContext)
├── screens/ # Screen components organized by feature
│ ├── Admin/ # Admin panel screens
│ │ ├── Donation/ # Donation management screens
│ │ ├── Voting/ # Voting management screens
│ │ ├── Event/ # Event management screens
│ │ └── ...
│ ├── Authentication/ # Login, registration flows
│ ├── RootLayout/ # Root layout components
│ └── ...
├── service/ # API services and business logic
│ ├── api-admin/ # Admin API endpoints
│ ├── api-client/ # Client API endpoints
│ └── api-config.ts # Axios configuration
├── hooks/ # Custom React hooks
│ ├── use-pagination.tsx # Pagination hook
│ └── ...
├── helpers/ # Helper functions
│ ├── paginationHelpers.tsx # Pagination UI helpers
│ └── ...
├── types/ # TypeScript type definitions
├── utils/ # Utility functions
├── constants/ # Constants and configuration values
├── styles/ # Global styles
├── assets/ # Images, icons, and static assets
└── docs/ # Documentation files
```
## Building and Running
### Prerequisites
- **Node.js**: v18+ with Bun package manager
- **Expo CLI**: Installed globally or via npx
- **iOS**: Xcode (macOS only) for iOS simulator/builds
- **Android**: Android Studio for Android emulator/builds
### Setup and Development
1. **Install Dependencies**
```bash
bun install
```
2. **Run Development Server**
```bash
bun run start
# or
bunx expo start
```
3. **Platform-Specific Commands**
```bash
# iOS Simulator
bun run ios
# or
bunx expo start --ios
# Android Emulator
bun run android
# or
bunx expo start --android
# Web Browser
bun run web
# or
bunx expo start --web
```
4. **Linting**
```bash
bun run lint
```
### Build Commands
#### EAS Build (Production)
```bash
# Production build (App Store / Play Store)
eas build --profile production
# Preview build (Internal distribution)
eas build --profile preview
# Development build (Development client)
eas build --profile development
```
#### Local Native Builds
```bash
# Generate native folders (iOS & Android)
npx expo prebuild
# iOS specific
bunx expo prebuild --platform ios
open ios/HIPMIBadungConnect.xcworkspace
# Android specific
bunx expo prebuild --platform android
```
#### Version Management
```bash
# Patch version update
npm version patch
# Update iOS build number
bunx expo prebuild --platform ios
# Update Android version code
bunx expo prebuild --platform android
```
### Android Debugging
```bash
# List connected devices
adb devices
# Install APK to device/emulator
adb install android/app/build/outputs/apk/debug/app-debug.apk
# Install to specific device
adb -s <device_id> install android/app/build/outputs/apk/debug/app-debug.apk
```
## Environment Variables
Create a `.env` file in the project root with:
```env
API_BASE_URL=https://your-api-base-url.com
BASE_URL=https://your-app-url.com
DEEP_LINK_URL=hipmimobile://
```
These are loaded in `app.config.js` and accessible via `Constants.expoConfig.extra`.
## Architecture Patterns
### 1. Separation of Concerns
**Route Files** (`app/`) should be minimal (max 5 lines):
```typescript
import { Admin_ScreenXXX } from "@/screens/Admin/XXX/ScreenXXX";
export default function AdminXXX() {
return <Admin_ScreenXXX />;
}
```
**Screen Components** (`screens/`) contain all business logic:
```typescript
export function Admin_ScreenXXX() {
// Logic, hooks, state management
return <NewWrapper ... />;
}
```
### 2. Pagination Pattern
Using `usePagination` hook with infinite scroll:
```typescript
const pagination = usePagination({
fetchFunction: async (page, searchQuery) => {
const response = await apiXXX({ page: String(page) });
if (response.success) {
return { data: response.data };
}
return { data: [] };
},
pageSize: PAGINATION_DEFAULT_TAKE, // 10
searchQuery: search,
dependencies: [dependency],
});
const { ListEmptyComponent, ListFooterComponent } =
createPaginationComponents({
loading: pagination.loading,
refreshing: pagination.refreshing,
listData: pagination.listData,
emptyMessage: "Belum ada data",
skeletonCount: PAGINATION_DEFAULT_TAKE,
});
```
### 3. Wrapper Components
**NewWrapper** (preferred for lists):
```typescript
<NewWrapper
listData={pagination.listData}
renderItem={renderItem}
headerComponent={headerComponent}
ListEmptyComponent={ListEmptyComponent}
ListFooterComponent={ListFooterComponent}
onEndReached={pagination.loadMore}
refreshControl={<RefreshControl ... />}
/>
```
**AdminBasicBox** (for card layouts):
```typescript
<AdminBasicBox
onPress={() => router.push(`/path/${item.id}`)}
style={{ marginHorizontal: 10, marginVertical: 5 }}
>
<StackCustom gap={0}>
<GridSpan_4_8 label="Label" value={<TextCustom>Value</TextCustom>} />
</StackCustom>
</AdminBasicBox>
```
### 4. API Service Structure
```typescript
// service/api-admin/api-xxx.ts
export async function apiXXX({ page = "1" }: { page?: string }) {
try {
const response = await apiConfig.get(`/mobile/admin/xxx?page=${page}`);
return response.data;
} catch (error) {
throw error;
}
}
```
**Important**: All list APIs should support pagination with `page` parameter (default: "1").
### 5. Authentication Flow
Managed by `AuthContext`:
- `loginWithNomor()` - Send phone number, receive OTP
- `validateOtp()` - Validate OTP, get token
- `registerUser()` - Register new user
- `logout()` - Clear session and logout
- `userData()` - Fetch user data by token
## Development Conventions
### Coding Standards
- **TypeScript**: Strict mode enabled
- **Naming**:
- Components: PascalCase (`Admin_ScreenDonationStatus`)
- Files: PascalCase for components (`ScreenDonationStatus.tsx`)
- Variables: camelCase
- Constants: UPPER_SNAKE_CASE
- **Path Aliases**: `@/*` maps to project root
- **Imports**: Group imports by type (components, hooks, services, etc.)
### Component Structure
```typescript
// 1. Imports (grouped)
import { ... } from "@/components";
import { ... } from "@/hooks";
import { ... } from "@/service";
// 2. Types/Interfaces
interface Props { ... }
// 3. Main Component
export function ComponentName() {
// State
// Hooks
// Functions
// Render
}
```
### Testing
- Linting: `bun run lint`
- No formal test suite configured yet
### Git Workflow
- Feature branches: `feature/xxx` or `fixed-admin/xxx`
- Commit messages: Clear and descriptive
- Use CHANGE_LOG.md for tracking changes
## Key Features
### Authentication
- Phone number login with OTP
- User registration
- Terms & Conditions acceptance
- Session persistence with AsyncStorage
### Admin Module
- **Dashboard**: Overview and statistics
- **User Access**: User management
- **Event**: Event CRUD with status management
- **Voting**: Voting management (publish/review/reject)
- **Donation**: Donation management with categories and transaction tracking
- **Collaboration**: Collaboration requests
- **Investment**: Investment management
- **Maps**: Location-based features
- **App Information**: Bank and business field management
### User Module
- **Home**: Main dashboard
- **Forum**: Discussion forums
- **Profile**: User profile management
- **Portfolio**: Member portfolio
- **Notifications**: Push notifications via Firebase
## API Configuration
### Base URLs
```typescript
// From app.config.js extra
API_BASE_URL: process.env.API_BASE_URL
BASE_URL: process.env.BASE_URL
DEEP_LINK_URL: process.env.DEEP_LINK_URL
```
### Axios Interceptor
All API calls use `apiConfig` with automatic token injection:
```typescript
apiConfig.interceptors.request.use(async (config) => {
const token = await AsyncStorage.getItem("authToken");
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
});
```
## Platform Configuration
### iOS
- **Bundle ID**: `com.anonymous.hipmi-mobile`
- **Build Number**: 21
- **Google Services**: Configured
- **Associated Domains**: `applinks:cld-dkr-staging-hipmi.wibudev.com`
- **Tablet Support**: Enabled
### Android
- **Package**: `com.bip.hipmimobileapp`
- **Version Code**: 4
- **Google Services**: Configured (`google-services.json`)
- **Deep Links**: HTTPS intent filters configured
- **Edge-to-Edge**: Enabled
### Web
- **Output**: Static
- **Bundler**: Metro
## Special Integrations
### Firebase
- Authentication
- Push Notifications (FCM)
- Configured for both iOS and Android
### Maplibre
- Map integration via `@maplibre/maplibre-react-native`
- Location permissions configured
### Deep Linking
- Scheme: `hipmimobile://`
- HTTPS: `cld-dkr-staging-hipmi.wibudev.com`
- Configured for both platforms
### Camera
- Camera and microphone permissions
- QR code generation support
## Common Development Tasks
### Adding a New Admin Screen
1. **Create Screen Component** (`screens/Admin/Feature/ScreenXXX.tsx`):
```typescript
export function Admin_ScreenXXX() {
const pagination = usePagination({...});
const renderItem = useCallback(...);
const headerComponent = useMemo(...);
return <NewWrapper ... />;
}
```
2. **Create Box Component** (optional, for custom item rendering):
```typescript
export default function Admin_BoxXXX({ item }: { item: any }) {
return (
<AdminBasicBox onPress={() => router.push(...)}>
...
</AdminBasicBox>
);
}
```
3. **Update API** (add pagination if needed):
```typescript
export async function apiXXX({ page = "1" }: { page?: string }) {
// ...
}
```
4. **Create Route File** (`app/(application)/admin/feature/xxx.tsx`):
```typescript
import { Admin_ScreenXXX } from "@/screens/Admin/Feature/ScreenXXX";
export default function AdminXXX() {
return <Admin_ScreenXXX />;
}
```
### Updating API Endpoints
1. Add function in appropriate service file
2. Include `page` parameter for list endpoints
3. Use `apiConfig` axios instance
4. Handle errors properly
## Troubleshooting
### Build Issues
```bash
# Clean and rebuild
rm -rf node_modules
bun install
bunx expo prebuild --clean
# iOS specific
cd ios && pod install && cd ..
# Android specific
cd android && ./gradlew clean && cd ..
```
### Cache Issues
```bash
# Clear Expo cache
bunx expo start -c
# Clear Metro cache
bunx expo start --clear
```
### Dependency Issues
```bash
# Reinstall dependencies
rm -rf node_modules bun.lock
bun install
```
### iOS Maplibre Crash Fix
When using Maplibre MapView on iOS, prevent "Attempt to recycle a mounted view" crash:
1. **Always render PointAnnotation** (not conditional)
2. **Use opacity for visibility** instead of conditional rendering
3. **Avoid key prop changes** that force remounting
```typescript
// ✅ GOOD: Stable PointAnnotation
<PointAnnotation
coordinate={annotationCoordinate} // Always rendered
...
>
<View style={{ opacity: selectedLocation ? 1 : 0 }}>
<SelectedLocationMarker />
</View>
</PointAnnotation>
// ❌ BAD: Conditional rendering causes crash
{selectedLocation && (
<PointAnnotation coordinate={selectedLocation} ... />
)}
```
## Documentation Files
- `docs/CHANGE_LOG.md` - Change log for recent updates
- `docs/hipmi-note.md` - Build and deployment notes
- `docs/prompt-for-qwen-code.md` - Development prompts and patterns
## Resources
- [Expo Documentation](https://docs.expo.dev/)
- [React Native Documentation](https://reactnative.dev/)
- [Expo Router Documentation](https://docs.expo.dev/router/introduction/)
- [TypeScript Documentation](https://www.typescriptlang.org/docs/)
- [Maplibre React Native](https://github.com/maplibre/maplibre-react-native)

View File

@@ -100,8 +100,8 @@ packagingOptions {
applicationId 'com.bip.hipmimobileapp'
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 4
versionName "1.0.1"
versionCode 1
versionName "1.0.2"
buildConfigField "String", "REACT_NATIVE_RELEASE_LEVEL", "\"${findProperty('reactNativeReleaseLevel') ?: 'stable'}\""
}

View File

@@ -37,7 +37,7 @@
</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="/"/>
<data android:scheme="https" android:host="cld-dkr-hipmi-stg.wibudev.com" android:pathPrefix="/"/>
<category android:name="android.intent.category.BROWSABLE"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>

View File

@@ -1,10 +1,21 @@
// app.config.js
require("dotenv").config();
// const isDev = process.env.NODE_ENV === "development";
// const isStaging = process.env.NEXT_PUBLIC_ENV === "staging";
// const isProd = process.env.NEXT_PUBLIC_ENV === "production";
// Domain berdasarkan environment
// const domain = isDev
// ? "localhost:3000"
// : isStaging
// ? "cld-dkr-hipmi-stg.wibudev.com"
// : "hipmi.muku.id"; // Production domain
export default {
name: "HIPMI Badung Connect",
slug: "hipmi-mobile",
version: "1.0.1",
version: "1.0.2",
orientation: "portrait",
icon: "./assets/images/icon.png",
scheme: "hipmimobile",
@@ -20,8 +31,10 @@ export default {
NSLocationWhenInUseUsageDescription:
"Aplikasi membutuhkan akses lokasi untuk menampilkan peta.",
},
associatedDomains: ["applinks:cld-dkr-staging-hipmi.wibudev.com"],
buildNumber: "19",
associatedDomains: [
"applinks:cld-dkr-hipmi-stg.wibudev.com",
],
buildNumber: "5",
},
android: {
@@ -32,7 +45,7 @@ export default {
},
edgeToEdgeEnabled: true,
package: "com.bip.hipmimobileapp",
versionCode: 4,
versionCode: 1,
// softwareKeyboardLayoutMode: 'resize', // option: untuk mengatur keyboard pada room chst collaboration
intentFilters: [
{
@@ -41,7 +54,7 @@ export default {
data: [
{
scheme: "https",
host: "cld-dkr-staging-hipmi.wibudev.com",
host: "cld-dkr-hipmi-stg.wibudev.com",
pathPrefix: "/",
},
],
@@ -77,7 +90,6 @@ export default {
},
],
"expo-font",
"@rnmapbox/maps",
"@react-native-firebase/app",
[
"expo-notifications",
@@ -87,6 +99,7 @@ export default {
iosDisplayInForeground: true,
},
],
"@maplibre/maplibre-react-native",
],
experiments: {

View File

@@ -1,8 +1,10 @@
import { CenterCustom, TextCustom, ViewWrapper } from "@/components";
import API_STRORAGE from "@/constants/base-url-api-strorage";
import { MainColor } from "@/constants/color-palet";
import { Image } from "expo-image";
import { useLocalSearchParams } from "expo-router";
import React, { useState } from "react";
import { View } from "react-native";
export default function PreviewImage() {
const { id } = useLocalSearchParams();
@@ -11,18 +13,48 @@ export default function PreviewImage() {
return (
<ViewWrapper>
{id ? (
<Image
onLoad={() => {
setIsLoading(false);
<View
style={{
width: "100%",
height: "100%",
position: "relative",
}}
source={
isLoading
? require("@/assets/images/loading.gif")
: API_STRORAGE.GET({ fileId: id as string })
}
contentFit="contain"
style={{ width: "100%", height: "100%" }}
/>
>
{/* Main Image */}
<Image
onLoad={() => {
setIsLoading(false);
}}
source={API_STRORAGE.GET({ fileId: id as string })}
contentFit="contain"
style={{ width: "100%", height: "100%" }}
// placeholder={require("@/assets/images/loading.gif")}
/>
{/* Custom Loader Overlay */}
{isLoading && (
<View
style={{
position: "absolute",
top: 0,
left: 0,
right: 0,
bottom: 0,
justifyContent: "center",
alignItems: "center",
backgroundColor: MainColor.darkblue,
zIndex: 1,
opacity: 0.5,
}}
>
<Image
source={require("@/assets/images/loading.gif")}
contentFit="contain"
style={{ width: 60, height: 60 }}
/>
</View>
)}
</View>
) : (
<CenterCustom>
<TextCustom>File not found</TextCustom>

View File

@@ -105,7 +105,7 @@ export default function TakePicture() {
</Pressable>
<Pressable onPress={pickImage}>
<AntDesign name="folderopen" size={32} color="white" />
<AntDesign name="folder-open" size={32} color="white" />
</Pressable>
</View>
</View>

View File

@@ -1,57 +1,9 @@
import {
FloatingButton,
LoaderCustom,
TextCustom,
ViewWrapper,
} from "@/components";
import Donation_BoxPublish from "@/screens/Donation/BoxPublish";
import { apiDonationGetAll } from "@/service/api-client/api-donation";
import { router, useFocusEffect } from "expo-router";
import _ from "lodash";
import { useCallback, useState } from "react";
import Donation_ScreenBeranda from "@/screens/Donation/ScreenBeranda";
export default function DonationBeranda() {
const [list, setList] = useState<any[] | null>(null);
const [loadList, setLoadList] = useState(false);
useFocusEffect(
useCallback(() => {
onLoadData();
}, [])
);
const onLoadData = async () => {
try {
setLoadList(true);
const response = await apiDonationGetAll({
category: "beranda"
});
console.log("[RES GET ALL]", JSON.stringify(response.data, null, 2));
setList(response.data);
} catch (error) {
console.log("[ERROR]", error);
} finally {
setLoadList(false);
}
};
return (
<ViewWrapper
hideFooter
floatingButton={
<FloatingButton onPress={() => router.push("/donation/create")} />
}
>
{loadList ? (
<LoaderCustom />
) : _.isEmpty(list) ? (
<TextCustom align="center" color="gray">Belum ada donasi</TextCustom>
) : (
list?.map((item: any, index: number) => (
<Donation_BoxPublish data={item} key={index} id={item.id} />
))
)}
</ViewWrapper>
<>
<Donation_ScreenBeranda />
</>
);
}

View File

@@ -1,142 +1,5 @@
/* eslint-disable react-hooks/exhaustive-deps */
import {
BadgeCustom,
BaseBox,
DummyLandscapeImage,
Grid,
LoaderCustom,
StackCustom,
TextCustom,
ViewWrapper,
} from "@/components";
import { useAuth } from "@/hooks/use-auth";
import { apiDonationGetAll } from "@/service/api-client/api-donation";
import { formatCurrencyDisplay } from "@/utils/formatCurrencyDisplay";
import { Href, router, useFocusEffect } from "expo-router";
import _ from "lodash";
import { useCallback, useState } from "react";
import { View } from "react-native";
import Donation_ScreenMyDonation from "@/screens/Donation/ScreenMyDonation";
export default function DonationMyDonation() {
const { user } = useAuth();
const [list, setList] = useState<any[] | null>(null);
const [loadList, setLoadList] = useState(false);
useFocusEffect(
useCallback(() => {
onLoadData();
}, [user?.id])
);
const onLoadData = async () => {
try {
setLoadList(true);
const response = await apiDonationGetAll({
category: "my-donation",
authorId: user?.id,
});
console.log(
"[RES GET MY DONATION]",
JSON.stringify(response.data, null, 2)
);
setList(response.data);
} catch (error) {
console.log("[ERROR]", error);
} finally {
setLoadList(false);
}
};
const handlerColor = (status: string) => {
if (status === "menunggu") {
return "orange";
} else if (status === "proses") {
return "white";
} else if (status === "berhasil") {
return "green";
} else if (status === "gagal") {
return "red";
}
};
const handlePress = ({
invoiceId,
donationId,
status,
}: {
invoiceId: string;
donationId: string;
status: string;
}) => {
const url: Href = `../${donationId}/(transaction-flow)/${invoiceId}`;
if (status === "menunggu") {
router.push(`${url}/invoice`);
} else if (status === "proses") {
router.push(`${url}/process`);
} else if (status === "berhasil") {
router.push(`${url}/success`);
} else if (status === "gagal") {
router.push(`${url}/failed`);
}
};
return (
<ViewWrapper hideFooter>
{loadList ? (
<LoaderCustom />
) : _.isEmpty(list) ? (
<TextCustom align="center" color="gray">
Belum ada transaksi
</TextCustom>
) : (
list?.map((item, index) => (
<BaseBox
key={index}
paddingTop={7}
paddingBottom={7}
onPress={() => {
handlePress({
status: _.lowerCase(item.statusInvoice),
invoiceId: item.id,
donationId: item.donasiId,
});
}}
>
<Grid>
<Grid.Col span={5}>
<DummyLandscapeImage
height={100}
unClickPath
imageId={item.imageId}
/>
</Grid.Col>
<Grid.Col span={1}>
<View />
</Grid.Col>
<Grid.Col span={6}>
<StackCustom>
<TextCustom truncate={2} bold>
{item.title || "-"}
</TextCustom>
<TextCustom bold color="yellow">
Rp. {formatCurrencyDisplay(item.nominal)}
</TextCustom>
<BadgeCustom
variant="light"
color={handlerColor(_.lowerCase(item.statusInvoice))}
fullWidth
>
{item.statusInvoice}
</BadgeCustom>
</StackCustom>
</Grid.Col>
</Grid>
</BaseBox>
))
)}
</ViewWrapper>
);
return <Donation_ScreenMyDonation />;
}

View File

@@ -1,80 +1,10 @@
/* eslint-disable react-hooks/exhaustive-deps */
import {
LoaderCustom,
ScrollableCustom,
TextCustom,
ViewWrapper,
} from "@/components";
import { useAuth } from "@/hooks/use-auth";
import { dummyMasterStatus } from "@/lib/dummy-data/_master/status";
import Donasi_BoxStatus from "@/screens/Donation/BoxStatus";
import { apiDonationGetByStatus } from "@/service/api-client/api-donation";
import { useFocusEffect } from "expo-router";
import _ from "lodash";
import { useCallback, useState } from "react";
import Donation_ScreenStatus from "@/screens/Donation/ScreenStatus";
import { useLocalSearchParams } from "expo-router";
export default function DonationStatus() {
const { user } = useAuth();
const [activeCategory, setActiveCategory] = useState<string | null>(
"publish"
);
const [listData, setListData] = useState<any[] | null>(null);
const [loadList, setLoadList] = useState(false);
useFocusEffect(
useCallback(() => {
onLoadList();
}, [activeCategory])
);
const onLoadList = async () => {
try {
setLoadList(true);
const response = await apiDonationGetByStatus({
authorId: user?.id as string,
status: activeCategory as string,
});
setListData(response.data);
} catch (error) {
console.log("[ERROR]", error);
setListData(null);
} finally {
setLoadList(false);
}
};
const handlePress = (item: any) => {
setActiveCategory(item.value);
// tambahkan logika lain seperti filter dsb.
};
const scrollComponent = (
<ScrollableCustom
data={dummyMasterStatus.map((e, i) => ({
id: i,
label: e.label,
value: e.value,
}))}
onButtonPress={handlePress}
activeId={activeCategory as any}
/>
);
const { status } = useLocalSearchParams<{ status?: string }>();
return (
<ViewWrapper hideFooter headerComponent={scrollComponent}>
{loadList ? (
<LoaderCustom />
) : _.isEmpty(listData) ? (
<TextCustom align="center">Tidak ada data {activeCategory}</TextCustom>
) : (
listData?.map((item: any, index: number) => (
<Donasi_BoxStatus
key={index}
data={item}
status={activeCategory as string}
/>
))
)}
</ViewWrapper>
<Donation_ScreenStatus initialStatus={status || "publish"} />
);
}

View File

@@ -1,5 +1,6 @@
/* eslint-disable react-hooks/exhaustive-deps */
import {
BoxButtonOnFooter,
ButtonCenteredOnly,
ButtonCustom,
InformationBox,
@@ -31,7 +32,7 @@ export default function DonationEditNews() {
useFocusEffect(
useCallback(() => {
onLoadData();
}, [news])
}, [news]),
);
const onLoadData = async () => {
@@ -104,7 +105,21 @@ export default function DonationEditNews() {
};
return (
<ViewWrapper>
<ViewWrapper
footerComponent={
<BoxButtonOnFooter>
<ButtonCustom
disabled={!data?.title || !data?.deskripsi}
isLoading={isLoading}
onPress={() => {
handlerSubmitUpdate();
}}
>
Update
</ButtonCustom>
</BoxButtonOnFooter>
}
>
<StackCustom gap={"xs"}>
<InformationBox text="Upload gambar bersifat opsional untuk melengkapi kabar terkait donasi Anda." />
<LandscapeFrameUploaded
@@ -148,15 +163,6 @@ export default function DonationEditNews() {
/>
<Spacing />
<ButtonCustom
disabled={!data?.title || !data?.deskripsi}
isLoading={isLoading}
onPress={() => {
handlerSubmitUpdate();
}}
>
Update
</ButtonCustom>
</StackCustom>
<Spacing />
</ViewWrapper>

View File

@@ -1,8 +1,10 @@
import {
BoxButtonOnFooter,
ButtonCenteredOnly,
ButtonCustom,
InformationBox,
LandscapeFrameUploaded,
NewWrapper,
Spacing,
StackCustom,
TextAreaCustom,
@@ -53,7 +55,7 @@ export default function DonationAddNews() {
text1: "Gagal menambah berita",
});
return
return;
}
Toast.show({
@@ -70,7 +72,21 @@ export default function DonationAddNews() {
};
return (
<ViewWrapper>
<NewWrapper
footerComponent={
<BoxButtonOnFooter>
<ButtonCustom
disabled={!data.title || !data.deskripsi}
isLoading={isLoading}
onPress={() => {
handlerSubmit();
}}
>
Simpan
</ButtonCustom>
</BoxButtonOnFooter>
}
>
<StackCustom gap={"xs"}>
<InformationBox text="Upload gambar bersifat opsional untuk melengkapi kabar terkait donasi Anda." />
<LandscapeFrameUploaded image={image?.uri} />
@@ -116,17 +132,7 @@ export default function DonationAddNews() {
/>
<Spacing />
<ButtonCustom
disabled={!data.title || !data.deskripsi}
isLoading={isLoading}
onPress={() => {
handlerSubmit();
}}
>
Simpan
</ButtonCustom>
</StackCustom>
<Spacing />
</ViewWrapper>
</NewWrapper>
);
}

View File

@@ -1,110 +1,8 @@
/* eslint-disable react-hooks/exhaustive-deps */
import {
BackButton,
BaseBox,
DrawerCustom,
Grid,
LoaderCustom,
MenuDrawerDynamicGrid,
TextCustom,
ViewWrapper,
} from "@/components";
import { IconPlus } from "@/components/_Icon";
import { apiDonationGetNewsById } from "@/service/api-client/api-donation";
import { formatChatTime } from "@/utils/formatChatTime";
import {
router,
Stack,
useFocusEffect,
useLocalSearchParams,
} from "expo-router";
import _ from "lodash";
import { useCallback, useState } from "react";
import { useLocalSearchParams } from "expo-router";
import Donation_ScreenListOfNews from "@/screens/Donation/ScreenListOfNews";
export default function DonationRecapOfNews() {
const { id } = useLocalSearchParams();
const [openDrawer, setOpenDrawer] = useState(false);
const [list, setList] = useState<any[] | null>(null);
const [loadList, setLoadList] = useState<boolean>(false);
useFocusEffect(
useCallback(() => {
onLoadList();
}, [id])
);
const onLoadList = async () => {
try {
setLoadList(true);
const response = await apiDonationGetNewsById({
id: id as string,
category: "get-all",
});
setList(response.data);
} catch (error) {
console.log("[ERROR]", error);
setList([]);
} finally {
setLoadList(false);
}
};
return (
<>
<Stack.Screen
options={{
title: "Daftar Kabar",
headerLeft: () => <BackButton />,
}}
/>
<ViewWrapper>
{loadList ? (
<LoaderCustom />
) : _.isEmpty(list) ? (
<TextCustom align="center" color="gray">
Tidak ada kabar
</TextCustom>
) : (
list?.map((item: any, index: number) => (
<BaseBox key={index} href={`/donation/[id]/(news)/${item.id}`}>
<Grid>
<Grid.Col span={8}>
<TextCustom truncate bold>
{item?.title || "-"}
</TextCustom>
</Grid.Col>
<Grid.Col span={4} style={{ alignItems: "flex-end" }}>
<TextCustom size="small">
{formatChatTime(item?.createdAt)}
</TextCustom>
</Grid.Col>
</Grid>
</BaseBox>
))
)}
</ViewWrapper>
<DrawerCustom
isVisible={openDrawer}
closeDrawer={() => setOpenDrawer(false)}
height={"auto"}
>
<MenuDrawerDynamicGrid
data={[
{
icon: <IconPlus />,
label: "Tambah Berita",
path: `/donation/${id}/(news)/add-news`,
},
]}
onPressItem={(item) => {
console.log("PATH ", item.path);
router.navigate(item.path as any);
setOpenDrawer(false);
}}
/>
</DrawerCustom>
</>
);
return <Donation_ScreenListOfNews donationId={id as string} />;
}

View File

@@ -1,112 +1,8 @@
/* eslint-disable react-hooks/exhaustive-deps */
import {
BackButton,
BaseBox,
DotButton,
DrawerCustom,
Grid,
LoaderCustom,
MenuDrawerDynamicGrid,
TextCustom,
ViewWrapper,
} from "@/components";
import { IconPlus } from "@/components/_Icon";
import { apiDonationGetNewsById } from "@/service/api-client/api-donation";
import { formatChatTime } from "@/utils/formatChatTime";
import {
router,
Stack,
useFocusEffect,
useLocalSearchParams,
} from "expo-router";
import _ from "lodash";
import { useCallback, useState } from "react";
import { useLocalSearchParams } from "expo-router";
import Donation_ScreenRecapOfNews from "@/screens/Donation/ScreenRecapOfNews";
export default function DonationRecapOfNews() {
const { id } = useLocalSearchParams();
const [openDrawer, setOpenDrawer] = useState(false);
const [list, setList] = useState<any[] | null>(null);
const [loadList, setLoadList] = useState<boolean>(false);
useFocusEffect(
useCallback(() => {
onLoadList();
}, [id])
);
const onLoadList = async () => {
try {
setLoadList(true);
const response = await apiDonationGetNewsById({
id: id as string,
category: "get-all",
});
setList(response.data);
} catch (error) {
console.log("[ERROR]", error);
setList([]);
} finally {
setLoadList(false);
}
};
return (
<>
<Stack.Screen
options={{
title: "Rekap Kabar",
headerLeft: () => <BackButton />,
headerRight: () => <DotButton onPress={() => setOpenDrawer(true)} />,
}}
/>
<ViewWrapper>
{loadList ? (
<LoaderCustom />
) : _.isEmpty(list) ? (
<TextCustom align="center" color="gray">
Tidak ada kabar
</TextCustom>
) : (
list?.map((item: any, index: number) => (
<BaseBox key={index} href={`/donation/[id]/(news)/${item.id}`}>
<Grid>
<Grid.Col span={8}>
<TextCustom truncate bold>
{item?.title || "-"}
</TextCustom>
</Grid.Col>
<Grid.Col span={4} style={{ alignItems: "flex-end" }}>
<TextCustom size="small">
{formatChatTime(item?.createdAt)}
</TextCustom>
</Grid.Col>
</Grid>
</BaseBox>
))
)}
</ViewWrapper>
<DrawerCustom
isVisible={openDrawer}
closeDrawer={() => setOpenDrawer(false)}
height={"auto"}
>
<MenuDrawerDynamicGrid
data={[
{
icon: <IconPlus />,
label: "Tambah Berita",
path: `/donation/${id}/(news)/add-news`,
},
]}
onPressItem={(item) => {
console.log("PATH ", item.path);
router.navigate(item.path as any);
setOpenDrawer(false);
}}
/>
</DrawerCustom>
</>
);
return <Donation_ScreenRecapOfNews donationId={id as string} />;
}

View File

@@ -1,6 +1,7 @@
/* eslint-disable react-hooks/exhaustive-deps */
import {
BaseBox,
BoxButtonOnFooter,
ButtonCenteredOnly,
ButtonCustom,
Grid,
@@ -35,7 +36,7 @@ export default function DonationInvoice() {
useFocusEffect(
useCallback(() => {
onLoadData();
}, [invoiceId])
}, [invoiceId]),
);
const onLoadData = async () => {
@@ -100,7 +101,22 @@ export default function DonationInvoice() {
return (
<>
<ViewWrapper>
<ViewWrapper
hideFooter
footerComponent={
<BoxButtonOnFooter>
<ButtonCustom
disabled={!image}
isLoading={isLoading}
onPress={() => {
handlerUpdateInvoice();
}}
>
Simpan
</ButtonCustom>
</BoxButtonOnFooter>
}
>
<StackCustom>
<InformationBox
text={`Mohon transfer donasi anda ke rekening dibawah`}
@@ -204,16 +220,6 @@ export default function DonationInvoice() {
</ButtonCenteredOnly>
</StackCustom>
</BaseBox>
<ButtonCustom
disabled={!image}
isLoading={isLoading}
onPress={() => {
handlerUpdateInvoice();
}}
>
Simpan
</ButtonCustom>
</StackCustom>
<Spacing />
</ViewWrapper>

View File

@@ -10,21 +10,32 @@ import {
import { MainColor } from "@/constants/color-palet";
import { ICON_SIZE_SMALL } from "@/constants/constans-value";
import { LOCAL_STORAGE_KEY } from "@/constants/local-storage-key";
import { useAuth } from "@/hooks/use-auth";
import { formatCurrencyDisplay } from "@/utils/formatCurrencyDisplay";
import { Ionicons } from "@expo/vector-icons";
import AsyncStorage from "@react-native-async-storage/async-storage";
import { router, useLocalSearchParams } from "expo-router";
import { useState } from "react";
import Toast from "react-native-toast-message";
export default function InvestmentInputDonation() {
const { user } = useAuth();
const { id } = useLocalSearchParams();
const [nominal, setNominal] = useState<number>(0);
const handlerSubmit = async () => {
if (!user?.id) {
Toast.show({
type: "error",
text1: "User tidak ditemukan",
});
return;
}
try {
await AsyncStorage.setItem(
LOCAL_STORAGE_KEY.transactionDonation,
JSON.stringify({ nominal: nominal.toString() })
JSON.stringify({ nominal: nominal.toString() }),
);
router.replace(`/donation/${id}/select-bank`);
} catch (error) {

View File

@@ -4,11 +4,12 @@ import {
DotButton,
DrawerCustom,
MenuDrawerDynamicGrid,
NewWrapper,
Spacing,
ViewWrapper,
} from "@/components";
import { IconEdit, IconNews } from "@/components/_Icon";
import { IMenuDrawerItem } from "@/components/_Interface/types";
import CustomSkeleton from "@/components/_ShareComponent/SkeletonCustom";
import { MainColor } from "@/constants/color-palet";
import { ICON_SIZE_SMALL } from "@/constants/constans-value";
import Donation_ButtonStatusSection from "@/screens/Donation/ButtonStatusSection";
@@ -26,18 +27,19 @@ import {
} from "expo-router";
import _ from "lodash";
import { useCallback, useEffect, useState } from "react";
import { RefreshControl } from "react-native";
export default function DonasiDetailStatus() {
const { id, status } = useLocalSearchParams();
const [openDrawer, setOpenDrawer] = useState(false);
const [openDrawerPublish, setOpenDrawerPublish] = useState(false);
const [data, setData] = useState<any>();
const [refreshing, setRefreshing] = useState(false);
const [data, setData] = useState<any | null>(null);
useFocusEffect(
useCallback(() => {
onLoadData();
}, [id])
}, [id]),
);
const onLoadData = async () => {
@@ -80,6 +82,17 @@ export default function DonasiDetailStatus() {
});
};
const onRefresh = useCallback(() => {
try {
setRefreshing(true);
onLoadData();
} catch (error) {
console.log("Error refresh");
} finally {
setRefreshing(false);
}
}, []);
return (
<>
<Stack.Screen
@@ -94,31 +107,50 @@ export default function DonasiDetailStatus() {
) : null,
}}
/>
<ViewWrapper>
<Donation_ComponentBoxDetailData
sisaHari={value.sisa}
reminder={value.reminder}
data={data}
bottomSection={
status === "publish" && (
<Donation_ProgressSection
<NewWrapper
refreshControl={
<RefreshControl
refreshing={refreshing}
onRefresh={onRefresh}
tintColor={MainColor.yellow}
colors={[MainColor.yellow]}
/>
}
>
{!data ? (
<CustomSkeleton height={400} />
) : (
<>
<Donation_ComponentBoxDetailData
sisaHari={value.sisa}
reminder={value.reminder}
data={data}
showSisaHari={status === "publish" ? true : false}
bottomSection={
status === "publish" && (
<Donation_ProgressSection
id={id as string}
progres={Number(data?.progres) || 0}
/>
)
}
/>
<Donation_ComponentStoryFunrising
id={id as string}
dataStory={data?.CeritaDonasi}
/>
<Spacing />
{data && (
<Donation_ButtonStatusSection
id={id as string}
progres={Number(data?.progres) || 0}
status={status as string}
/>
)
}
/>
<Donation_ComponentStoryFunrising
id={id as string}
dataStory={data?.CeritaDonasi}
/>
<Spacing />
<Donation_ButtonStatusSection
id={id as string}
status={status as string}
/>
<Spacing />
</ViewWrapper>
)}
<Spacing />
</>
)}
</NewWrapper>
<DrawerCustom
isVisible={openDrawer}

View File

@@ -1,16 +1,19 @@
/* eslint-disable react-hooks/exhaustive-deps */
import {
BoxButtonOnFooter,
ButtonCenteredOnly,
ButtonCustom,
InformationBox,
LandscapeFrameUploaded,
LoaderCustom,
NewWrapper,
SelectCustom,
Spacing,
StackCustom,
TextInputCustom,
ViewWrapper,
} from "@/components";
import ListSkeletonComponent from "@/components/_ShareComponent/ListSkeletonComponent";
import API_IMAGE from "@/constants/api-storage";
import DIRECTORY_ID from "@/constants/directory-id";
import {
@@ -60,7 +63,7 @@ export default function DonationEdit() {
useCallback(() => {
onLoadData();
onLoadList();
}, [id])
}, [id]),
);
const onLoadData = async () => {
@@ -79,7 +82,6 @@ export default function DonationEdit() {
imageId: response.data.imageId,
});
}
} catch (error) {
console.log("[ERROR]", error);
}
@@ -182,10 +184,24 @@ export default function DonationEdit() {
};
return (
<ViewWrapper>
<NewWrapper
hideFooter
footerComponent={
<BoxButtonOnFooter>
<ButtonCustom
isLoading={isLoading}
onPress={() => {
handlerSubmitUpdate();
}}
>
Update
</ButtonCustom>
</BoxButtonOnFooter>
}
>
<InformationBox text="Lengkapi semua data di bawah untuk selanjutnya mengisi cerita penggalangan dana." />
{!data || loadList ? (
<LoaderCustom />
<ListSkeletonComponent />
) : (
<StackCustom gap={"xs"}>
<TextInputCustom
@@ -260,17 +276,9 @@ export default function DonationEdit() {
/>
<Spacing />
<ButtonCustom
isLoading={isLoading}
onPress={() => {
handlerSubmitUpdate();
}}
>
Update
</ButtonCustom>
</StackCustom>
)}
<Spacing />
</ViewWrapper>
</NewWrapper>
);
}

View File

@@ -1,124 +1,8 @@
/* 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, useFocusEffect, useLocalSearchParams } from "expo-router";
import _ from "lodash";
import React, { useState } from "react";
import { useLocalSearchParams } from "expo-router";
import Donation_ScreenFundDisbursement from "@/screens/Donation/ScreenFundDisbursement";
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>
<InformationBox text="Pencairan dana akan dilakukan oleh Admin HIPMI tanpa campur tangan pihak manapun, jika berita pencairan dana dibawah tidak sesuai dengan kabar yang diberikan oleh PENGGALANG DANA. Maka pegguna lain dapat melaporkannya pada Admin HIPMI !" />
<BaseBox>
<Grid>
<Grid.Col span={6}>
<TextCustom bold color="yellow">
Rp. {formatCurrencyDisplay(data?.totalPencairan)}
</TextCustom>
<TextCustom size="small">Total Pencairan Dana</TextCustom>
</Grid.Col>
<Grid.Col span={6}>
<TextCustom bold color="yellow">
{data?.akumulasiPencairan} kali
</TextCustom>
<TextCustom size="small">Akumulasi Pencairan</TextCustom>
</Grid.Col>
</Grid>
</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>
</>
);
return <Donation_ScreenFundDisbursement donationId={id as string} />;
}

View File

@@ -6,10 +6,12 @@ import {
DotButton,
DrawerCustom,
MenuDrawerDynamicGrid,
NewWrapper,
StackCustom,
ViewWrapper,
} from "@/components";
import { IconNews } from "@/components/_Icon";
import CustomSkeleton from "@/components/_ShareComponent/SkeletonCustom";
import { useAuth } from "@/hooks/use-auth";
import Donation_ComponentBoxDetailData from "@/screens/Donation/ComponentBoxDetailData";
import Donation_ComponentInfoFundrising from "@/screens/Donation/ComponentInfoFundrising";
@@ -34,7 +36,7 @@ export default function DonasiDetailBeranda() {
useFocusEffect(
useCallback(() => {
onLoadData();
}, [id])
}, [id]),
);
const onLoadData = async () => {
@@ -75,10 +77,10 @@ export default function DonasiDetailBeranda() {
<>
<BoxButtonOnFooter>
<ButtonCustom
disabled={value?.reminder}
disabled={value?.reminder || !data}
onPress={() => router.navigate(`/donation/${id}/(transaction-flow)`)}
>
{value?.reminder ? "Waktu berakhir" : "Donasi"}
{!data ? "Loading..." : value?.reminder ? "Waktu berakhir" : "Donasi"}
</ButtonCustom>
</BoxButtonOnFooter>
</>
@@ -96,21 +98,30 @@ export default function DonasiDetailBeranda() {
) : null,
}}
/>
<ViewWrapper footerComponent={buttonSection}>
<StackCustom>
<Donation_ComponentBoxDetailData
sisaHari={value.sisa}
reminder={value.reminder}
data={data}
bottomSection={<Donation_ProgressSection id={id as string} progres={Number(data?.progres) || 0} />}
/>
<Donation_ComponentInfoFundrising dataAuthor={data?.Author} />
<Donation_ComponentStoryFunrising
id={id as string}
dataStory={data?.CeritaDonasi}
/>
</StackCustom>
</ViewWrapper>
<NewWrapper footerComponent={buttonSection}>
{!data ? (
<CustomSkeleton height={400} />
) : (
<StackCustom>
<Donation_ComponentBoxDetailData
sisaHari={value.sisa}
reminder={value.reminder}
data={data}
bottomSection={
<Donation_ProgressSection
id={id as string}
progres={Number(data?.progres) || 0}
/>
}
/>
<Donation_ComponentInfoFundrising dataAuthor={data?.Author} />
<Donation_ComponentStoryFunrising
id={id as string}
dataStory={data?.CeritaDonasi}
/>
</StackCustom>
)}
</NewWrapper>
<DrawerCustom
isVisible={openDrawer}

View File

@@ -1,94 +1,8 @@
/* 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";
import { useLocalSearchParams } from "expo-router";
import Donation_ScreenListOfDonatur from "@/screens/Donation/ScreenListOfDonatur";
export default function Donation_ListOfDonatur() {
export default function DonationListOfDonatur() {
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>
{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">
{item?.Author?.username || "-"}
</TextCustom>
<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>
</>
);
return <Donation_ScreenListOfDonatur donationId={id as string} />;
}

View File

@@ -1,5 +1,6 @@
/* eslint-disable react-hooks/exhaustive-deps */
import {
BoxButtonOnFooter,
ButtonCenteredOnly,
ButtonCustom,
InformationBox,
@@ -8,8 +9,8 @@ import {
StackCustom,
TextAreaCustom,
TextInputCustom,
ViewWrapper,
} from "@/components";
import NewWrapper from "@/components/_ShareComponent/NewWrapper";
import DIRECTORY_ID from "@/constants/directory-id";
import { useAuth } from "@/hooks/use-auth";
import {
@@ -103,7 +104,7 @@ export default function DonationCreateStory() {
type: "success",
text1: "Donasi berhasil disimpan",
});
router.replace("/donation/status");
router.replace("/donation/status?status=review");
} catch (error) {
console.log("[ERROR]", error);
} finally {
@@ -112,7 +113,23 @@ export default function DonationCreateStory() {
};
return (
<ViewWrapper>
<NewWrapper
hideFooter
footerComponent={
<>
<BoxButtonOnFooter>
<ButtonCustom
isLoading={isLoading}
onPress={() => {
handlerSubmit();
}}
>
Simpan
</ButtonCustom>
</BoxButtonOnFooter>
</>
}
>
<StackCustom gap={"xs"}>
<InformationBox text="Cerita Anda adalah kunci untuk menginspirasi kebaikan. Jelaskan dengan jujur dan jelas tujuan penggalangan dana ini agar calon donatur memahami dampak positif yang dapat mereka wujudkan melalui kontribusi mereka." />
<TextAreaCustom
@@ -166,18 +183,8 @@ export default function DonationCreateStory() {
value={data.rekening}
onChangeText={(value) => setData({ ...data, rekening: value })}
/>
<Spacing />
<ButtonCustom
isLoading={isLoading}
onPress={() => {
handlerSubmit();
}}
>
Simpan
</ButtonCustom>
</StackCustom>
<Spacing />
</ViewWrapper>
</NewWrapper>
);
}

View File

@@ -1,4 +1,5 @@
import {
BoxButtonOnFooter,
ButtonCenteredOnly,
ButtonCustom,
InformationBox,
@@ -8,8 +9,8 @@ import {
Spacing,
StackCustom,
TextInputCustom,
ViewWrapper,
} from "@/components";
import NewWrapper from "@/components/_ShareComponent/NewWrapper";
import DIRECTORY_ID from "@/constants/directory-id";
import { apiDonationCreate } from "@/service/api-client/api-donation";
import { apiMasterDonation } from "@/service/api-client/api-master";
@@ -43,7 +44,7 @@ export default function DonationCreate() {
useFocusEffect(
useCallback(() => {
onLoadList();
}, [])
}, []),
);
const onLoadList = async () => {
@@ -125,7 +126,24 @@ export default function DonationCreate() {
};
return (
<ViewWrapper>
<NewWrapper
hideFooter
footerComponent={
<>
<BoxButtonOnFooter>
<ButtonCustom
isLoading={isLoading}
onPress={() => {
handlerSubmit();
// router.push(`/donation/create-story?id=${"dasdsadsa"}`);
}}
>
Selanjutnya
</ButtonCustom>
</BoxButtonOnFooter>
</>
}
>
<StackCustom gap={"xs"}>
<InformationBox text="Lengkapi semua data di bawah untuk selanjutnya mengisi cerita penggalangan dana." />
@@ -201,20 +219,8 @@ export default function DonationCreate() {
onChange={(value: any) => setData({ ...data, durasiId: value })}
/>
)}
<Spacing />
<ButtonCustom
isLoading={isLoading}
onPress={() => {
handlerSubmit();
// router.push(`/donation/create-story?id=${"dasdsadsa"}`);
}}
>
Selanjutnya
</ButtonCustom>
<Spacing />
</StackCustom>
<Spacing />
</ViewWrapper>
</NewWrapper>
);
}

View File

@@ -1,115 +1,10 @@
/* eslint-disable react-hooks/exhaustive-deps */
import {
AvatarUsernameAndOtherComponent,
BoxWithHeaderSection,
LoaderCustom,
Spacing,
StackCustom,
TextCustom,
ViewWrapper
} from "@/components";
import { useAuth } from "@/hooks/use-auth";
import {
apiEventGetAll
} from "@/service/api-client/api-event";
import { dateTimeView } from "@/utils/dateTimeView";
import { useFocusEffect } from "expo-router";
import _ from "lodash";
import React, { useCallback, useState } from "react";
import Event_ScreenContribution from "@/screens/Event/ScreenContribution";
export default function EventContribution() {
const { user } = useAuth();
const [listData, setListData] = useState<any>([]);
const [isLoadList, setIsLoadList] = useState(false);
useFocusEffect(
useCallback(() => {
onLoadData();
}, [user?.id])
);
async function onLoadData() {
try {
setIsLoadList(true);
const response = await apiEventGetAll({
category: "contribution",
userId: user?.id,
});
console.log("[DATA] ", JSON.stringify(response.data, null, 2));
if (response.success) {
setListData(response.data);
// const responseListParticipants = await apiEventListOfParticipants({
// id: response?.data?.Event?.id,
// });
// console.log(
// "[LIST PARTICIPANTS]",
// JSON.stringify(responseListParticipants.data, null, 2)
// );
// if (responseListParticipants.success) {
// setListParticipants(responseListParticipants.data);
// }
}
} catch (error) {
console.log("[ERROR]", error);
} finally {
setIsLoadList(false);
}
}
return (
<ViewWrapper hideFooter>
{isLoadList ? (
<LoaderCustom />
) : _.isEmpty(listData) ? (
<TextCustom align="center">Belum ada kontribusi</TextCustom>
) : (
listData.map((item: any, index: number) => (
<BoxWithHeaderSection
key={index}
href={`/event/${item?.Event?.id}/contribution`}
>
<StackCustom>
<AvatarUsernameAndOtherComponent
avatar={item?.Event?.Author?.Profile?.imageId}
avatarHref={`/profile/${item?.Event?.Author?.Profile?.id}`}
name={item?.Event?.Author?.username}
rightComponent={
<TextCustom truncate>
{dateTimeView({
date: item?.Event?.tanggal,
withoutTime: true,
})}
</TextCustom>
}
/>
<TextCustom bold align="center" size="xlarge" truncate={2}>
{item?.Event?.title}
</TextCustom>
<Spacing height={0} />
{/* <Grid>
{item?.Event?.Event_Peserta?.map(
(item2: any, index2: number) => (
<Grid.Col
style={{ alignItems: "center" }}
span={12 / item?.Event?.Event_Peserta?.length}
key={index2}
>
<AvatarComp
size="base"
href={`/profile/${item2?.User?.Profile?.id}`}
fileId={item2?.User?.Profile?.imageId}
/>
</Grid.Col>
)
)}
</Grid> */}
</StackCustom>
</BoxWithHeaderSection>
))
)}
</ViewWrapper>
<>
<Event_ScreenContribution />
</>
);
}

View File

@@ -1,104 +1,10 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { ButtonCustom, LoaderCustom, Spacing, TextCustom } from "@/components";
import ViewWrapper from "@/components/_ShareComponent/ViewWrapper";
import { AccentColor, MainColor } from "@/constants/color-palet";
import { useAuth } from "@/hooks/use-auth";
import Event_BoxPublishSection from "@/screens/Event/BoxPublishSection";
import { apiEventGetAll } from "@/service/api-client/api-event";
import { dateTimeView } from "@/utils/dateTimeView";
import _ from "lodash";
import { useEffect, useState } from "react";
import { View } from "react-native";
import Event_ScreenHistory from "@/screens/Event/ScreenHistory";
export default function EventHistory() {
const [activeCategory, setActiveCategory] = useState<string | null>("all");
const { user } = useAuth();
const [listData, setListData] = useState<any>([]);
const [isLoadList, setIsLoadList] = useState(false);
useEffect(() => {
onLoadData({ userId: user?.id });
}, [user?.id, activeCategory]);
async function onLoadData({ userId }: { userId?: string }) {
try {
setIsLoadList(true);
const response = await apiEventGetAll({
category: activeCategory === "all" ? "all-history" : "my-history",
userId: userId,
});
if (response.success) {
setListData(response.data);
}
} catch (error) {
console.log("[ERROR]", error);
} finally {
setIsLoadList(false);
}
}
const handlePress = (item: any) => {
setActiveCategory(item);
// tambahkan logika lain seperti filter dsb.
};
const headerComponent = (
<View
style={{
flexDirection: "row",
alignItems: "center",
padding: 5,
backgroundColor: MainColor.soft_darkblue,
borderRadius: 50,
width: "100%",
}}
>
<ButtonCustom
backgroundColor={
activeCategory === "all" ? MainColor.yellow : AccentColor.blue
}
textColor={activeCategory === "all" ? MainColor.black : MainColor.white}
style={{ width: "49%" }}
onPress={() => handlePress("all")}
>
Semua Riwayat
</ButtonCustom>
<Spacing width={"2%"} />
<ButtonCustom
backgroundColor={
activeCategory === "main" ? MainColor.yellow : AccentColor.blue
}
textColor={
activeCategory === "main" ? MainColor.black : MainColor.white
}
style={{ width: "49%" }}
onPress={() => handlePress("main")}
>
Riwayat Saya
</ButtonCustom>
</View>
);
return (
<ViewWrapper headerComponent={headerComponent} hideFooter>
{isLoadList ? (
<LoaderCustom />
) : _.isEmpty(listData) ? (
<TextCustom align="center">Belum ada riwayat</TextCustom>
) : (
listData.map((item: any, index: number) => (
<Event_BoxPublishSection
key={index.toString()}
data={item}
rightComponentAvatar={
<TextCustom>
{dateTimeView({ date: item?.tanggal, withoutTime: true })}
</TextCustom>
}
href={`/event/${item.id}/history`}
/>
))
)}
</ViewWrapper>
<>
<Event_ScreenHistory />
</>
);
}

View File

@@ -1,63 +1,9 @@
import { LoaderCustom, TextCustom } from "@/components";
import ViewWrapper from "@/components/_ShareComponent/ViewWrapper";
import FloatingButton from "@/components/Button/FloatingButton";
import Event_BoxPublishSection from "@/screens/Event/BoxPublishSection";
import { apiEventGetAll } from "@/service/api-client/api-event";
import { dateTimeView } from "@/utils/dateTimeView";
import { router, useFocusEffect } from "expo-router";
import _ from "lodash";
import { useCallback, useState } from "react";
import Event_ScreenBeranda from "@/screens/Event/ScreenBeranda";
export default function EventBeranda() {
const [listData, setListData] = useState([]);
const [isLoadData, setIsLoadData] = useState(false);
useFocusEffect(
useCallback(() => {
onLoadData();
}, [])
);
const onLoadData = async () => {
try {
setIsLoadData(true);
const response = await apiEventGetAll({category: "beranda"});
// console.log("Response", JSON.stringify(response.data, null, 2));
setListData(response.data);
} catch (error) {
console.log("[ERROR]", error);
} finally {
setIsLoadData(false);
}
};
return (
<ViewWrapper
hideFooter
floatingButton={
<FloatingButton onPress={() => router.push("/event/create")} />
}
>
{isLoadData ? (
<LoaderCustom />
) : _.isEmpty(listData) ? (
<TextCustom align="center">Belum ada event</TextCustom>
) : (
listData.map((item: any, index) => (
<Event_BoxPublishSection
key={index}
href={`/event/${item.id}/publish`}
data={item}
rightComponentAvatar={
<TextCustom>
{dateTimeView({ date: item?.tanggal, withoutTime: true })}
</TextCustom>
}
/>
))
)}
</ViewWrapper>
<>
<Event_ScreenBeranda />
</>
);
}

View File

@@ -1,101 +1,10 @@
/* eslint-disable react-hooks/exhaustive-deps */
import {
BoxWithHeaderSection,
Grid,
LoaderCustom,
ScrollableCustom,
StackCustom,
TextCustom,
} from "@/components";
import ViewWrapper from "@/components/_ShareComponent/ViewWrapper";
import { useAuth } from "@/hooks/use-auth";
import { dummyMasterStatus } from "@/lib/dummy-data/_master/status";
import { apiEventGetByStatus } from "@/service/api-client/api-event";
import { useFocusEffect, useLocalSearchParams } from "expo-router";
import _ from "lodash";
import { useCallback, useState } from "react";
import Event_ScreenStatus from "@/screens/Event/ScreenStatus";
export default function EventStatus() {
const { user } = useAuth();
const { status } = useLocalSearchParams<{ status?: string }>();
const id = user?.id || "";
const [activeCategory, setActiveCategory] = useState<string | null>(
status || "publish"
);
const [listData, setListData] = useState([]);
const [loadingGetData, setLoadingGetData] = useState(false);
useFocusEffect(
useCallback(() => {
onLoadData();
}, [activeCategory, id])
);
async function onLoadData() {
try {
setLoadingGetData(true);
const response = await apiEventGetByStatus({
id: id!,
status: activeCategory!,
});
// console.log("Response", JSON.stringify(response.data, null, 2));
setListData(response.data);
} catch (error) {
console.log(error);
} finally {
setLoadingGetData(false);
}
}
const handlePress = (item: any) => {
setActiveCategory(item.value);
// tambahkan logika lain seperti filter dsb.
};
const tabsComponent = (
<ScrollableCustom
data={dummyMasterStatus.map((e, i) => ({
id: i,
label: e.label,
value: e.value,
}))}
onButtonPress={handlePress}
activeId={activeCategory as any}
/>
);
return (
<ViewWrapper headerComponent={tabsComponent}>
{loadingGetData ? (
<LoaderCustom />
) : _.isEmpty(listData) ? (
<TextCustom align="center">Tidak ada data {activeCategory}</TextCustom>
) : (
listData.map((item: any, i) => (
<BoxWithHeaderSection
key={i}
href={`/event/${item.id}/${activeCategory}/detail-event`}
>
<StackCustom gap={"xs"}>
<Grid>
<Grid.Col span={8}>
<TextCustom truncate bold>
{item?.title}
</TextCustom>
</Grid.Col>
<Grid.Col span={4} style={{ alignItems: "flex-end" }}>
<TextCustom>
{new Date(item?.tanggal).toLocaleDateString()}
</TextCustom>
</Grid.Col>
</Grid>
<TextCustom truncate={2}>{item?.deskripsi}</TextCustom>
</StackCustom>
</BoxWithHeaderSection>
))
)}
</ViewWrapper>
<>
<Event_ScreenStatus />
</>
);
}

View File

@@ -9,7 +9,7 @@ import {
TextCustom,
ViewWrapper,
} from "@/components";
import { AccentColor, MainColor } from "@/constants/color-palet";
import { MainColor } from "@/constants/color-palet";
import { useAuth } from "@/hooks/use-auth";
import {
apiEventConfirmationAction,
@@ -60,7 +60,7 @@ export default function UserEventConfirmation() {
useFocusEffect(
useCallback(() => {
checkTokenAndDataParticipants() || console.log("Token is null");
}, [token, id, user?.id])
}, [token, id, user?.id]),
);
const checkTokenAndDataParticipants = async () => {
@@ -113,7 +113,7 @@ export default function UserEventConfirmation() {
confirmationStart,
confirmationEnd,
null,
"[]"
"[]",
);
// --- [4] Status waktu event (untuk pesan UI) ---
@@ -218,9 +218,14 @@ export default function UserEventConfirmation() {
if (isWithinConfirmationWindow) {
if (konfirmasi === false) {
return (
<TamplateBox data={data}>
<TamplateText text="Konfirmasi Kehadiran" />
</TamplateBox>
// <TamplateBox data={data}>
// <TamplateText text="Konfirmasi Kehadiran" />
// </TamplateBox>
<UserParticipan_And_DuringEvent
id={data.id}
userId={user?.id as string}
data={data}
/>
);
}
return (
@@ -261,17 +266,15 @@ export default function UserEventConfirmation() {
<Stack.Screen
options={{
title: "Konfirmasi Event",
// headerLeft: () => (
// <Ionicons
// name="arrow-back"
// size={20}
// color={MainColor.yellow}
// onPress={() =>
// router.navigate("/(application)/(user)/event/create")
// }
// />
// ),
}}
headerLeft: () => (
<Ionicons
name="arrow-back"
size={20}
color={MainColor.yellow}
onPress={() => router.navigate("/")}
/>
),
}}
/>
<ViewWrapper>{handlerReturn()}</ViewWrapper>
</>
@@ -497,7 +500,6 @@ const UserNotParticipan_And_DuringEvent = ({
);
};
// 🟡 ZONA ACARA BERLANGSUN
// User sudah terdaftar & Event sedang berlangsung & user harus konfirmasi
const UserParticipan_And_DuringEvent = ({

View File

@@ -1,7 +1,9 @@
/* eslint-disable react-hooks/exhaustive-deps */
import {
BoxButtonOnFooter,
ButtonCustom,
LoaderCustom,
NewWrapper,
SelectCustom,
Spacing,
StackCustom,
@@ -10,6 +12,7 @@ import {
TextInputCustom,
ViewWrapper,
} from "@/components";
import ListSkeletonComponent from "@/components/_ShareComponent/ListSkeletonComponent";
import DateTimePickerCustom from "@/components/DateInput/DateTimePickerCustom";
import {
apiEventGetOne,
@@ -48,14 +51,14 @@ export default function EventEdit() {
useFocusEffect(
useCallback(() => {
onLoadData();
}, [id])
}, [id]),
);
async function onLoadData() {
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));
@@ -100,6 +103,15 @@ export default function EventEdit() {
const startDate = new Date(selectedDate as any);
const endDate = new Date(selectedEndDate as any);
if (!startDate) {
Toast.show({
type: "info",
text1: "Info",
text2: "Tanggal mulai tidak valid",
});
return false;
}
if (startDate >= endDate) {
Toast.show({
type: "info",
@@ -146,7 +158,7 @@ export default function EventEdit() {
const validateDateRange = (
selectedDate: string | Date,
selectedEndDate: string | Date
selectedEndDate: string | Date,
): { isValid: boolean; error?: string } => {
const startDate = new Date(selectedDate);
const endDate = new Date(selectedEndDate);
@@ -174,9 +186,19 @@ export default function EventEdit() {
return (
<>
<ViewWrapper>
<NewWrapper
footerComponent={
<BoxButtonOnFooter>
<ButtonCustom
isLoading={isLoading}
title="Update"
onPress={handlerSubmit}
/>
</BoxButtonOnFooter>
}
>
{isLoadData ? (
<LoaderCustom />
<ListSkeletonComponent />
) : (
<StackCustom gap={"xs"}>
<TextInputCustom
@@ -186,26 +208,15 @@ export default function EventEdit() {
value={data?.title}
onChangeText={(value) => setData({ ...data, title: value })}
/>
<SelectCustom
label="Tipe Event"
placeholder="Pilih tipe event"
data={listTypeEvent.map((item: any) => ({
label: item.name,
value: item.id,
}))}
value={data?.eventMaster_TipeAcaraId || ""}
onChange={(value) => {
console.log(value);
setData({ ...data, eventMaster_TipeAcaraId: value });
}}
/>
<TextInputCustom
label="Lokasi"
placeholder="Masukkan lokasi event"
<TextAreaCustom
label="Deskripsi"
placeholder="Masukkan deskripsi event"
required
value={data?.lokasi}
onChangeText={(value) => setData({ ...data, lokasi: value })}
showCount
value={data?.deskripsi}
onChangeText={(value) => setData({ ...data, deskripsi: value })}
/>
<DateTimePickerCustom
minimumDate={new Date(Date.now())}
label="Tanggal & Waktu Mulai"
@@ -233,7 +244,7 @@ export default function EventEdit() {
{
validateDateRange(
selectedDate as any,
selectedEndDate as any
selectedEndDate as any,
).error
}
</TextCustom>
@@ -242,31 +253,37 @@ export default function EventEdit() {
{
validateDateRange(
selectedDate as any,
selectedEndDate as any
selectedEndDate as any,
).error
}
</TextCustom>
)}
<Spacing />
{/* <Spacing /> */}
</StackCustom>
<TextAreaCustom
label="Deskripsi"
placeholder="Masukkan deskripsi event"
required
showCount
value={data?.deskripsi}
onChangeText={(value) => setData({ ...data, deskripsi: value })}
<SelectCustom
label="Tipe Event"
placeholder="Pilih tipe event"
data={listTypeEvent.map((item: any) => ({
label: item.name,
value: item.id,
}))}
value={data?.eventMaster_TipeAcaraId || ""}
onChange={(value) => {
console.log(value);
setData({ ...data, eventMaster_TipeAcaraId: value });
}}
/>
<ButtonCustom
isLoading={isLoading}
title="Update"
onPress={handlerSubmit}
<TextInputCustom
label="Lokasi"
placeholder="Masukkan lokasi event"
required
value={data?.lokasi}
onChangeText={(value) => setData({ ...data, lokasi: value })}
/>
</StackCustom>
)}
</ViewWrapper>
</NewWrapper>
</>
);
}

View File

@@ -1,110 +1,10 @@
/* eslint-disable react-hooks/exhaustive-deps */
import {
AvatarUsernameAndOtherComponent,
BadgeCustom,
BaseBox,
LoaderCustom,
TextCustom,
ViewWrapper,
} from "@/components";
import {
apiEventGetOne,
apiEventListOfParticipants,
} from "@/service/api-client/api-event";
import dayjs, { Dayjs } from "dayjs";
import { useFocusEffect, useLocalSearchParams } from "expo-router";
import _ from "lodash";
import { useCallback, useState } from "react";
import { View } from "react-native";
import Event_ScreenListOfParticipants from "@/screens/Event/ScreenListOfParticipants";
export default function EventListOfParticipants() {
const { id } = useLocalSearchParams();
const [startDate, setStartDate] = useState<Dayjs | undefined>();
const [listData, setListData] = useState<any[] | null>(null);
const [loadtData, setLoadData] = useState(false);
useFocusEffect(
useCallback(() => {
handlerLoadData();
}, [id])
);
const handlerLoadData = () => {
try {
onLoadData();
onLoadList();
} catch (error) {
console.log("[ERROR]", error);
}
};
const onLoadData = async () => {
try {
const response = await apiEventGetOne({ id: id as string });
if (response.success) {
const date = dayjs(response.data.tanggal);
setStartDate(date);
}
} catch (error) {
console.log("[ERROR]", error);
}
};
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>
{loadtData && !listData ? (
<LoaderCustom />
) : _.isEmpty(listData) ? (
<TextCustom align="center" color="gray">
Belum ada peserta
</TextCustom>
) : (
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={
startDate && startDate.subtract(1, "hour").diff(dayjs()) < 0 ? (
<View
style={{
justifyContent: "flex-end",
}}
>
<BadgeCustom color={item?.isPresent ? "green" : "red"}>
{item?.isPresent ? "Hadir" : "Tidak Hadir"}
</BadgeCustom>
</View>
) : (
<View
style={{
justifyContent: "flex-end",
}}
>
<BadgeCustom color="gray">-</BadgeCustom>
</View>
)
}
/>
</BaseBox>
))
)}
</ViewWrapper>
<>
<Event_ScreenListOfParticipants />
</>
);
}

View File

@@ -1,12 +1,13 @@
import {
BoxButtonOnFooter,
ButtonCustom,
NewWrapper,
SelectCustom,
Spacing,
StackCustom,
TextAreaCustom,
TextCustom,
TextInputCustom,
ViewWrapper,
} from "@/components";
import DateTimePickerCustom from "@/components/DateInput/DateTimePickerCustom";
import { useAuth } from "@/hooks/use-auth";
@@ -100,7 +101,6 @@ export default function EventCreate() {
setIsLoading(false);
}
};
const buttonSubmit = (
<ButtonCustom
@@ -112,7 +112,9 @@ export default function EventCreate() {
return (
<>
<ViewWrapper>
<NewWrapper
footerComponent={<BoxButtonOnFooter>{buttonSubmit}</BoxButtonOnFooter>}
>
<StackCustom gap={"xs"}>
<TextInputCustom
placeholder="Masukkan nama event"
@@ -121,24 +123,15 @@ export default function EventCreate() {
onChangeText={(value: any) => setData({ ...data, title: value })}
/>
<SelectCustom
label="Tipe Event"
placeholder="Pilih tipe event"
data={listTypeEvent.map((item: any) => ({
label: item.name,
value: item.id,
}))}
value={data?.eventMaster_TipeAcaraId || null}
onChange={(value: any) =>
setData({ ...data, eventMaster_TipeAcaraId: value })
}
/>
<TextInputCustom
label="Lokasi"
placeholder="Masukkan lokasi event"
<TextAreaCustom
label="Deskripsi"
placeholder="Masukkan deskripsi event"
required
onChangeText={(value: any) => setData({ ...data, lokasi: value })}
showCount
value={data?.deskripsi || ""}
onChangeText={(value: any) =>
setData({ ...data, deskripsi: value })
}
/>
<DateTimePickerCustom
@@ -168,22 +161,28 @@ export default function EventCreate() {
</TextCustom>
)}
<Spacing />
<SelectCustom
label="Tipe Event"
placeholder="Pilih tipe event"
data={listTypeEvent.map((item: any) => ({
label: item.name,
value: item.id,
}))}
value={data?.eventMaster_TipeAcaraId || null}
onChange={(value: any) =>
setData({ ...data, eventMaster_TipeAcaraId: value })
}
/>
<TextInputCustom
label="Lokasi"
placeholder="Masukkan lokasi event"
required
onChangeText={(value: any) => setData({ ...data, lokasi: value })}
/>
</StackCustom>
<TextAreaCustom
label="Deskripsi"
placeholder="Masukkan deskripsi event"
required
showCount
value={data?.deskripsi || ""}
onChangeText={(value: any) =>
setData({ ...data, deskripsi: value })
}
/>
{buttonSubmit}
</StackCustom>
</ViewWrapper>
</NewWrapper>
</>
);
}

View File

@@ -1,258 +1,11 @@
import {
ButtonCustom,
DrawerCustom,
LoaderCustom,
Spacing,
TextAreaCustom,
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";
import Forum_MenuDrawerBerandaSection from "@/screens/Forum/MenuDrawerSection.tsx/MenuBeranda";
import Forum_MenuDrawerCommentar from "@/screens/Forum/MenuDrawerSection.tsx/MenuCommentar";
import {
apiForumCreateComment,
apiForumGetComment,
apiForumGetOne,
apiForumUpdateStatus,
} from "@/service/api-client/api-forum";
import { TypeForum_CommentProps } from "@/types/type-forum";
import { isBadContent } from "@/utils/badWordsIndonesia";
import { useFocusEffect, useLocalSearchParams } from "expo-router";
import _ from "lodash";
import { useCallback, useEffect, useState } from "react";
import DetailForum from "@/screens/Forum/DetailForum";
import DetailForum2 from "@/screens/Forum/DetailForum2";
export default function ForumDetail() {
const { id } = useLocalSearchParams();
const { user } = useAuth();
const [openDrawer, setOpenDrawer] = useState(false);
const [data, setData] = useState<any | null>(null);
const [listComment, setListComment] = useState<TypeForum_CommentProps[] | null>(null);
const [isLoadingComment, setLoadingComment] = useState(false);
// Status
const [status, setStatus] = useState("");
const [text, setText] = useState("");
const [authorId, setAuthorId] = useState("");
const [dataId, setDataId] = useState("");
// Comentar
const [openDrawerCommentar, setOpenDrawerCommentar] = useState(false);
const [commentId, setCommentId] = useState("");
const [commentAuthorId, setCommentAuthorId] = useState("");
useFocusEffect(
useCallback(() => {
onLoadData(id as string);
}, [id])
);
const onLoadData = async (id: string) => {
try {
const response = await apiForumGetOne({ id });
setData(response.data);
} catch (error) {
console.log("[ERROR]", error);
}
};
useEffect(() => {
onLoadListComment(id as string);
}, [id]);
const onLoadListComment = async (id: string) => {
try {
const response = await apiForumGetComment({
id: id as string,
});
setListComment(response.data);
} catch (error) {
console.log("[ERROR]", error);
}
};
// Update Status
const handlerUpdateStatus = async (value: any) => {
try {
const response = await apiForumUpdateStatus({
id: id as string,
data: value,
});
if (response.success) {
setStatus(response.data);
setData({
...data,
ForumMaster_StatusPosting: {
status: response.data,
},
});
}
} catch (error) {
console.log("[ERROR]", error);
}
};
// Create Commentar
const handlerCreateCommentar = async () => {
if (isBadContent(text)) {
AlertWarning({});
return;
}
const newData = {
comment: text,
authorId: user?.id,
};
try {
setLoadingComment(true);
const response = await apiForumCreateComment({
id: id as string,
data: newData,
});
if (response.success) {
setText("");
const newComment = {
id: response.data.id,
isActive: response.data.isActive,
komentar: response.data.komentar,
createdAt: response.data.createdAt,
authorId: response.data.authorId,
Author: response.data.Author,
};
setListComment((prev) => [newComment, ...(prev || [])]);
setData({
...data,
count: data.count + 1,
});
}
} catch (error) {
console.log("[ERROR]", error);
} finally {
setLoadingComment(false);
}
};
return (
<>
<ViewWrapper>
{!data && !listComment ? (
<LoaderCustom />
) : (
<>
{/* Box Posting */}
<Forum_BoxDetailSection
data={data}
onSetData={() => {
setOpenDrawer(true);
setStatus(data.ForumMaster_StatusPosting?.status);
setAuthorId(data.Author?.id);
setDataId(data.id);
}}
/>
{/* Area Commentar */}
{data?.ForumMaster_StatusPosting?.status === "Open" && (
<>
<TextAreaCustom
placeholder="Ketik diskusi anda..."
maxLength={1000}
showCount
value={text}
onChangeText={setText}
style={{
marginBottom: 0,
}}
/>
<ButtonCustom
isLoading={isLoadingComment}
style={{
alignSelf: "flex-end",
}}
onPress={() => {
handlerCreateCommentar();
}}
>
Balas
</ButtonCustom>
</>
)}
<Spacing height={40} />
{/* List Commentar */}
{_.isEmpty(listComment) ? (
<TextCustom align="center" color="gray" size={"small"}>
Tidak ada komentar
</TextCustom>
) : (
<TextCustom color="gray">Komentar :</TextCustom>
)}
<Spacing height={5} />
{listComment?.map((item: any, index: number) => (
<Forum_CommentarBoxSection
key={index}
data={item}
onSetData={(value) => {
setCommentId(value.setCommentId);
setOpenDrawerCommentar(value.setOpenDrawer);
setCommentAuthorId(value.setCommentAuthorId);
}}
/>
))}
</>
)}
</ViewWrapper>
{/* Posting Drawer */}
<DrawerCustom
height={"auto"}
isVisible={openDrawer}
closeDrawer={() => setOpenDrawer(false)}
>
<Forum_MenuDrawerBerandaSection
id={dataId}
authorUsername={data?.Author?.username as string}
status={status}
setIsDrawerOpen={() => {
setOpenDrawer(false);
}}
authorId={authorId}
handlerUpdateStatus={(value: any) => {
handlerUpdateStatus(value);
}}
/>
</DrawerCustom>
{/* Commentar Drawer */}
<DrawerCustom
height={"auto"}
isVisible={openDrawerCommentar}
closeDrawer={() => setOpenDrawerCommentar(false)}
>
<Forum_MenuDrawerCommentar
id={commentId as string}
commentId={commentId}
commentAuthorId={commentAuthorId}
setIsDrawerOpen={() => {
setOpenDrawerCommentar(false);
}}
listComment={listComment}
setListComment={setListComment}
countComment={data?.count}
setCountComment={(val: any) => {
setData((prev: any) => ({
...prev,
count: val,
}));
}}
/>
</DrawerCustom>
{/* <DetailForum />; */}
<DetailForum2 />
</>
);
)
}

View File

@@ -1,12 +1,14 @@
/* eslint-disable react-hooks/exhaustive-deps */
import Forum_ViewBeranda from "@/screens/Forum/ViewBeranda";
import Forum_ViewBeranda2 from "@/screens/Forum/ViewBeranda2";
import Forum_ViewBeranda3 from "@/screens/Forum/ViewBeranda3";
export default function Forum() {
return (
<>
{/* <Forum_ViewBeranda /> */}
<Forum_ViewBeranda2 />
{/* <Forum_ViewBeranda2 /> */}
<Forum_ViewBeranda3 />
</>
);
}

View File

@@ -1,115 +1,208 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
/* eslint-disable react-hooks/exhaustive-deps */
import { StackCustom, ViewWrapper } from "@/components";
import { BasicWrapper, StackCustom, ViewWrapper } from "@/components";
import AppHeader from "@/components/_ShareComponent/AppHeader";
import CustomSkeleton from "@/components/_ShareComponent/SkeletonCustom";
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 { stylesHome } from "@/screens/Home/homeViewStyle";
import Home_ImageSection from "@/screens/Home/imageSection";
import TabSection from "@/screens/Home/tabSection";
import { tabsHome } from "@/screens/Home/tabsList";
import Home_FeatureSection from "@/screens/Home/topFeatureSection";
import { apiJobGetAll } from "@/service/api-client/api-job";
import { apiUser } from "@/service/api-client/api-user";
import { apiVersion } from "@/service/api-config";
import { GStyles } from "@/styles/global-styles";
import { Ionicons } from "@expo/vector-icons";
import { Redirect, router, Stack, useFocusEffect } from "expo-router";
import { useCallback, useState } from "react";
import { RefreshControl } from "react-native";
import { RefreshControl, View } from "react-native";
export default function Application() {
const { token, user, userData } = useAuth();
const [data, setData] = useState<any>();
const [refreshing, setRefreshing] = useState(false);
const { syncUnreadCount } = useNotificationStore();
const [listData, setListData] = useState<any[] | null>(null);
useFocusEffect(
useCallback(() => {
onLoadData();
onLoadDataJob();
checkVersion();
userData(token as string);
userData(token as string).catch((error) => {
console.log("[ERROR userData]", error?.message);
console.log("[ERROR userData Response]", error?.response?.data);
});
syncUnreadCount();
}, [user?.id, token])
}, [user?.id, token]),
);
async function onLoadData() {
const response = await apiUser(user?.id as string);
console.log(
"[Profile ID]>>",
JSON.stringify(response?.data?.Profile?.id, null, 2)
);
setData(response.data);
try {
const response = await apiUser(user?.id as string);
setData(response.data);
} catch (error: any) {
console.log("[ERROR onLoadData]", error?.message);
console.log("[ERROR Response]", error?.response?.data);
// Set data tetap agar UI tidak stuck di loading
setData(null);
}
}
const onLoadDataJob = async () => {
try {
const response = await apiJobGetAll({
category: "beranda",
});
const result = response.data
.sort(
(a: any, b: any) =>
new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime(),
)
.slice(0, 2);
setListData(result);
} catch (error) {
console.log("[ERROR]", error);
}
};
const checkVersion = async () => {
const response = await apiVersion();
console.log("[Version] >>", JSON.stringify(response.data, null, 2));
try {
const response = await apiVersion();
console.log("[Version] >>", JSON.stringify(response.data, null, 2));
} catch (error: any) {
console.log("[ERROR checkVersion]", error?.message);
}
};
const onRefresh = useCallback(() => {
setRefreshing(true);
onLoadData();
onLoadDataJob();
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`} />;
console.warn("User is not active");
return (
<BasicWrapper>
<Redirect href={`/waiting-room`} />
</BasicWrapper>
);
}
if (data && data?.Profile === null) {
console.log("Profile is null");
return <Redirect href={`/profile/create`} />;
console.warn("Profile is null");
return (
<BasicWrapper>
<Redirect href={`/profile/create`} />
</BasicWrapper>
);
}
// if (data && data?.masterUserRoleId !== "1") {
// console.log("User is not admin");
// return (
// <BasicWrapper>
// <Redirect href={`/admin/dashboard`} />
// </BasicWrapper>
// );
// }
return (
<>
<Stack.Screen
options={{
title: `HIPMI`,
headerLeft: () => (
<Ionicons
name="search"
size={20}
color={MainColor.yellow}
onPress={() => {
router.push("/user-search");
}}
header: () => (
<AppHeader
title="HIPMI"
showBack={false}
left={
data ? (
<Ionicons
name="search"
size={20}
color={MainColor.yellow}
onPress={() => {
router.push("/user-search");
}}
/>
) : (
<CustomSkeleton height={30} width={30} radius={100} />
)
}
right={
data ? (
<HeaderBell />
) : (
<CustomSkeleton height={30} width={30} radius={100} />
)
}
/>
),
headerRight: () => <HeaderBell />,
}}
/>
<ViewWrapper
refreshControl={
<RefreshControl refreshing={refreshing} onRefresh={onRefresh} />
<RefreshControl
refreshing={refreshing}
onRefresh={onRefresh}
tintColor={MainColor.yellow}
colors={[MainColor.yellow]}
/>
}
footerComponent={
<TabSection
tabs={tabsHome({
acceptedForumTermsAt: data?.acceptedForumTermsAt,
profileId: data?.Profile?.id,
})}
/>
data && data ? (
<TabSection
tabs={tabsHome({
acceptedForumTermsAt: data?.acceptedForumTermsAt,
profileId: data?.Profile?.id,
})}
/>
) : (
<View style={GStyles.tabBar}>
<View style={[GStyles.tabContainer, { paddingTop: 10 }]}>
{Array.from({ length: 4 }).map((e, index) => (
<CustomSkeleton
key={index}
height={40}
width={40}
radius={100}
/>
))}
</View>
</View>
)
}
>
<StackCustom>
{/* <ButtonCustom onPress={() => router.push("./test-notifications")}>
Test Notif
</ButtonCustom> */}
<Home_ImageSection />
<Home_FeatureSection />
{data && data ? (
<Home_FeatureSection />
) : (
<View style={stylesHome.gridContainer}>
{Array.from({ length: 4 }).map((item, index) => (
<CustomSkeleton
key={index}
style={stylesHome.gridItem}
radius={50}
/>
))}
</View>
)}
<Home_BottomFeatureSection />
{data ? (
<Home_BottomFeatureSection listData={listData} />
) : (
<CustomSkeleton height={200} />
)}
</StackCustom>
</ViewWrapper>
</>

View File

@@ -1,9 +1,33 @@
import BackButtonFromNotification from "@/components/Button/BackButtonFromNotification";
import { ICON_SIZE_SMALL } from "@/constants/constans-value";
import { TabsStyles } from "@/styles/tabs-styles";
import { Feather, FontAwesome6, 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 InvestmentTabsLayout() {
// const navigation = useNavigation();
// const { from, category } = useLocalSearchParams<{
// from?: string;
// category?: string;
// }>();
// console.log("from", from);
// console.log("category", category);
// // Atur header secara dinamis
// useLayoutEffect(() => {
// navigation.setOptions({
// headerLeft: () => (
// <BackButtonFromNotification
// from={from as string}
// category={category as string}
// />
// ),
// });
// }, [from, router, navigation]);
return (
<Tabs screenOptions={TabsStyles}>
<Tabs.Screen

View File

@@ -1,56 +1,9 @@
import {
FloatingButton,
LoaderCustom,
ViewWrapper
} from "@/components";
import NoDataText from "@/components/_ShareComponent/NoDataText";
import Investment_BoxBerandaSection from "@/screens/Invesment/BoxBerandaSection";
import { apiInvestmentGetAll } from "@/service/api-client/api-investment";
import { router, useFocusEffect } from "expo-router";
import _ from "lodash";
import { useCallback, useState } from "react";
import Investment_ScreenBursa from "@/screens/Invesment/ScreenBursa";
export default function InvestmentBursa() {
const [list, setList] = useState<any[] | null>(null);
const [loadingList, setLoadingList] = useState(false);
useFocusEffect(
useCallback(() => {
onLoadList();
}, [])
);
const onLoadList = async () => {
try {
setLoadingList(true);
const response = await apiInvestmentGetAll({
category: "bursa"
});
// console.log("[DATA LIST]", JSON.stringify(response.data, null, 2));
setList(response.data);
} catch (error) {
console.log("[ERROR]", error);
} finally {
setLoadingList(false);
}
};
return (
<ViewWrapper
hideFooter
floatingButton={
<FloatingButton onPress={() => router.push("/investment/create")} />
}
>
{loadingList ? (
<LoaderCustom />
) : _.isEmpty(list) ? (
<NoDataText />
) : (
list?.map((item: any, index: number) => (
<Investment_BoxBerandaSection id={item.id} data={item} key={index} />
))
)}
</ViewWrapper>
<>
<Investment_ScreenBursa />
</>
);
}

View File

@@ -1,83 +1,10 @@
/* eslint-disable react-hooks/exhaustive-deps */
import {
BaseBox,
Grid,
LoaderCustom,
ProgressCustom,
Spacing,
StackCustom,
TextCustom,
ViewWrapper,
} from "@/components";
import NoDataText from "@/components/_ShareComponent/NoDataText";
import { useAuth } from "@/hooks/use-auth";
import { apiInvestmentGetAll } from "@/service/api-client/api-investment";
import { formatCurrencyDisplay } from "@/utils/formatCurrencyDisplay";
import { router, useFocusEffect } from "expo-router";
import _ from "lodash";
import React, { useCallback, useState } from "react";
import { View } from "react-native";
import Investment_ScreenMyHolding from "@/screens/Invesment/ScreenMyHolding";
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>
{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}`)
}
>
<StackCustom>
<TextCustom truncate={2}>{item?.title}</TextCustom>
<TextCustom>
Rp. {formatCurrencyDisplay(item?.nominal)}
</TextCustom>
<TextCustom>{item?.lembarTerbeli} Lembar</TextCustom>
<ProgressCustom
label={`${item.progress}%`}
value={Number(item.progress)}
size="lg"
animated
color="primary"
/>
</StackCustom>
</BaseBox>
))
)}
</ViewWrapper>
<>
<Investment_ScreenMyHolding />
</>
);
}

View File

@@ -1,80 +1,10 @@
/* eslint-disable react-hooks/exhaustive-deps */
import {
LoaderCustom,
ScrollableCustom,
TextCustom,
ViewWrapper,
} from "@/components";
import { useAuth } from "@/hooks/use-auth";
import { dummyMasterStatus } from "@/lib/dummy-data/_master/status";
import Investment_StatusBox from "@/screens/Invesment/StatusBox";
import { apiInvestmentGetByStatus } from "@/service/api-client/api-investment";
import { useFocusEffect } from "expo-router";
import _ from "lodash";
import { useCallback, useState } from "react";
import Investment_ScreenPortofolio from "@/screens/Invesment/ScreenPortofolio";
export default function InvestmentPortofolio() {
const { user } = useAuth();
const [activeCategory, setActiveCategory] = useState<string | null>(
"publish"
);
const [listData, setListData] = useState<any[]>([]);
const [loadingList, setLoadingList] = useState(false);
useFocusEffect(
useCallback(() => {
onLoadData();
}, [user?.id, activeCategory])
);
const onLoadData = async () => {
try {
setLoadingList(true);
const response = await apiInvestmentGetByStatus({
authorId: user?.id as string,
status: activeCategory as string,
});
setListData(response.data);
} catch (error) {
console.log("[ERROR]", error);
} finally {
setLoadingList(false);
}
};
const handlePress = (item: any) => {
setActiveCategory(item.value);
// tambahkan logika lain seperti filter dsb.
};
const scrollComponent = (
<ScrollableCustom
data={dummyMasterStatus.map((e, i) => ({
id: i,
label: e.label,
value: e.value,
}))}
onButtonPress={handlePress}
activeId={activeCategory as any}
/>
);
return (
<ViewWrapper headerComponent={scrollComponent} hideFooter>
{loadingList ? (
<LoaderCustom />
) : _.isEmpty(listData) ? (
<TextCustom align="center">Tidak ada data {activeCategory}</TextCustom>
) : (
listData.map((item: any, index: number) => (
<Investment_StatusBox
key={index}
data={item}
status={activeCategory as string}
href={`/investment/${item.id}/${activeCategory}/detail`}
/>
))
)}
</ViewWrapper>
<>
<Investment_ScreenPortofolio />
</>
);
}

View File

@@ -1,124 +1,10 @@
/* eslint-disable react-hooks/exhaustive-deps */
import {
BadgeCustom,
BaseBox,
Grid,
LoaderCustom,
StackCustom,
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";
import { formatChatTime } from "@/utils/formatChatTime";
import { formatCurrencyDisplay } from "@/utils/formatCurrencyDisplay";
import { router, useFocusEffect } from "expo-router";
import _ from "lodash";
import { useCallback, useState } from "react";
import { View } from "react-native";
import Investment_ScreenTransaction from "@/screens/Invesment/ScreenTransaction";
export default function InvestmentTransaction() {
const { user } = useAuth();
const [list, setList] = useState<any>([]);
const [loadList, setLoadList] = useState<boolean>(false);
useFocusEffect(
useCallback(() => {
onLoadList();
}, [user?.id])
);
const onLoadList = async () => {
try {
setLoadList(true);
const response = await apiInvestmentGetInvoice({
authorId: user?.id as string,
category: "transaction",
});
console.log("[RESPONSE LIST]", JSON.stringify(response.data, null, 2));
setList(response.data);
} catch (error) {
console.log("[ERROR]", error);
} finally {
setLoadList(false);
}
};
const handlerColor = (status: string) => {
if (status === "menunggu") {
return "orange";
} else if (status === "proses") {
return "white";
} else if (status === "berhasil") {
return "green";
} else if (status === "gagal") {
return "red";
}
};
const handlePress = ({ id, status }: { id: string; status: string }) => {
if (status === "menunggu") {
router.push(`/investment/${id}/(transaction-flow)/invoice`);
} else if (status === "proses") {
router.push(`/investment/${id}/(transaction-flow)/process`);
} else if (status === "berhasil") {
router.push(`/investment/${id}/(transaction-flow)/success`);
} else if (status === "gagal") {
router.push(`/investment/${id}/(transaction-flow)/failed`);
}
};
return (
<ViewWrapper hideFooter>
{loadList ? (
<LoaderCustom />
) : _.isEmpty(list) ? (
<NoDataText/>
) : (
list.map((item: any, i: number) => (
<BaseBox
key={i}
paddingTop={7}
paddingBottom={7}
onPress={() => {
handlePress({
id: item.id,
status: _.lowerCase(item.statusInvoice),
});
}}
>
<Grid>
<Grid.Col span={6}>
<StackCustom gap={"xs"}>
<TextCustom truncate>{item?.title || "-"}</TextCustom>
<TextCustom color="gray" size="small">
{formatChatTime(item?.createdAt)}
</TextCustom>
</StackCustom>
</Grid.Col>
<Grid.Col span={1}>
<View />
</Grid.Col>
<Grid.Col span={5} style={{ alignItems: "flex-end" }}>
<StackCustom gap={"xs"}>
<TextCustom bold truncate>
Rp. {formatCurrencyDisplay(item?.nominal) || "-"}
</TextCustom>
<BadgeCustom
variant="light"
color={handlerColor(_.lowerCase(item.statusInvoice))}
style={GStyles.alignSelfFlexEnd}
>
{item?.statusInvoice || "-"}
</BadgeCustom>
</StackCustom>
</Grid.Col>
</Grid>
</BaseBox>
))
)}
</ViewWrapper>
<>
<Investment_ScreenTransaction />
</>
);
}

View File

@@ -1,58 +1,10 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { LoaderCustom, TextCustom, ViewWrapper } from "@/components";
import Investment_BoxDetailDocument from "@/screens/Invesment/Document/RecapBoxDetail";
import { apiInvestmentGetDocument } from "@/service/api-client/api-investment";
import { useFocusEffect, useLocalSearchParams } from "expo-router";
import _ from "lodash";
import { useCallback, useState } from "react";
import Investment_ScreenListOfDocument from "@/screens/Invesment/Document/ScreenListDocument";
export default function InvestmentListOfDocument() {
const { id } = useLocalSearchParams();
console.log("ID >> ", id);
const [list, setList] = useState<any[] | null>(null);
const [loadList, setLoadList] = useState(false);
useFocusEffect(
useCallback(() => {
onLoadListDocument();
}, [id])
);
const onLoadListDocument = async () => {
try {
setLoadList(true);
const response = await apiInvestmentGetDocument({
id: id as string,
category: "all-document",
});
setList(response.data);
} catch (error) {
console.log("[ERROR]", error);
setList([]);
} finally {
setLoadList(false);
}
};
return (
<ViewWrapper>
{loadList ? (
<LoaderCustom />
) : _.isEmpty(list) ? (
<TextCustom align="center" color="gray">
Tidak ada data
</TextCustom>
) : (
list?.map((item: any, index: number) => (
<Investment_BoxDetailDocument
key={index}
title={item.title}
href={`/(file)/${item.fileId}`}
/>
))
)}
</ViewWrapper>
<>
<Investment_ScreenListOfDocument />
</>
);
}

View File

@@ -1,213 +1,10 @@
/* eslint-disable react-hooks/exhaustive-deps */
import {
AlertDefaultSystem,
BackButton,
DotButton,
DrawerCustom,
LoaderCustom,
MenuDrawerDynamicGrid,
TextCustom,
ViewWrapper,
} from "@/components";
import { IconEdit } from "@/components/_Icon";
import { MainColor } from "@/constants/color-palet";
import { ICON_SIZE_SMALL } from "@/constants/constans-value";
import Investment_BoxDetailDocument from "@/screens/Invesment/Document/RecapBoxDetail";
import {
apiInvestmentDeleteDocument,
apiInvestmentGetDocument,
} from "@/service/api-client/api-investment";
import { AntDesign, Ionicons } from "@expo/vector-icons";
import {
router,
Stack,
useFocusEffect,
useLocalSearchParams,
} from "expo-router";
import _ from "lodash";
import { useCallback, useState } from "react";
import Toast from "react-native-toast-message";
import Investment_ScreenRecapOfDocument from "@/screens/Invesment/Document/ScreenRecapOfDocument";
export default function InvestmentRecapOfDocument() {
const { id } = useLocalSearchParams();
const [openDrawer, setOpenDrawer] = useState(false);
const [openDrawerBox, setOpenDrawerBox] = useState(false);
const [list, setList] = useState<any[] | null>(null);
const [loadList, setLoadList] = useState(false);
const [selectId, setSelectId] = useState<string | null>(null);
useFocusEffect(
useCallback(() => {
onLoadListDocument();
}, [id])
);
const onLoadListDocument = async () => {
try {
setLoadList(true);
const response = await apiInvestmentGetDocument({
id: id as string,
category: "all-document",
});
setList(response.data);
} catch (error) {
console.log("[ERROR]", error);
setList([]);
} finally {
setLoadList(false);
}
};
const handlerDeleteDocument = async () => {
try {
const response = await apiInvestmentDeleteDocument({
id: selectId as string,
});
if (response.success) {
Toast.show({
type: "success",
text1: "Data berhasil dihapus",
});
setList((prev: any[] | null) => {
if (!prev) return null;
return prev.filter((item: any) => item.id !== selectId);
});
setOpenDrawerBox(false);
setSelectId(null);
}
} catch (error) {
console.log("[ERROR]", error);
Toast.show({
type: "error",
text1: "Gagal menghapus data",
});
}
};
return (
<>
<Stack.Screen
options={{
title: "Rekap Dokumen",
headerLeft: () => <BackButton />,
headerRight: () => (
<DotButton
onPress={() => {
setOpenDrawer(true);
setOpenDrawerBox(false);
}}
/>
),
}}
/>
<ViewWrapper>
{loadList ? (
<LoaderCustom />
) : _.isEmpty(list) ? (
<TextCustom align="center" color="gray">
Tidak ada data
</TextCustom>
) : (
list?.map((item: any, index: number) => (
<Investment_BoxDetailDocument
key={index}
title={item.title}
leftIcon={
<Ionicons
name="ellipsis-horizontal-outline"
size={ICON_SIZE_SMALL}
color={MainColor.white}
style={{
zIndex: 10,
alignSelf: "flex-end",
}}
onPress={() => {
setSelectId(item.id);
setOpenDrawerBox(true);
}}
/>
}
href={`/(file)/${item.fileId}`}
/>
))
)}
</ViewWrapper>
{/* Drawer On Header */}
<DrawerCustom
isVisible={openDrawer}
closeDrawer={() => setOpenDrawer(false)}
height={"auto"}
>
<MenuDrawerDynamicGrid
data={[
{
icon: (
<AntDesign
name="plus-circle"
size={ICON_SIZE_SMALL}
color={MainColor.white}
/>
),
label: "Tambah Dokumen",
path: `/investment/${id}/(document)/add-document`,
},
]}
onPressItem={(item) => {
router.push(item.path as any);
setOpenDrawer(false);
}}
/>
</DrawerCustom>
{/* Drawer On Box */}
<DrawerCustom
isVisible={openDrawerBox}
closeDrawer={() => setOpenDrawerBox(false)}
height={"auto"}
>
<MenuDrawerDynamicGrid
data={[
{
icon: <IconEdit />,
label: "Edit Dokumen",
path: `/investment/${selectId}/(document)/edit-document`,
},
{
icon: (
<Ionicons
name="trash-outline"
size={ICON_SIZE_SMALL}
color={MainColor.white}
/>
),
label: "Hapus Dokumen",
path: "" as any,
color: MainColor.red,
},
]}
onPressItem={(item) => {
if (item.path === ("" as any)) {
AlertDefaultSystem({
title: "Hapus Dokumen",
message: "Apakah anda yakin ingin menghapus dokumen ini?",
textLeft: "Batal",
textRight: "Hapus",
onPressRight: () => {
handlerDeleteDocument();
},
});
} else {
router.push(item.path as any);
}
setOpenDrawerBox(false);
}}
/>
</DrawerCustom>
<Investment_ScreenRecapOfDocument />
</>
);
}

View File

@@ -115,7 +115,11 @@ export default function InvestmentAddNews() {
onChangeText={(value) => setData({ ...data, deskripsi: value })}
/>
<ButtonCustom isLoading={isLoading} onPress={handlerSubmit}>
<ButtonCustom
disabled={!data.title || !data.deskripsi || isLoading}
isLoading={isLoading}
onPress={handlerSubmit}
>
Simpan
</ButtonCustom>
</StackCustom>

View File

@@ -1,100 +1,10 @@
/* eslint-disable react-hooks/exhaustive-deps */
import {
BackButton,
BaseBox,
DrawerCustom,
LoaderCustom,
MenuDrawerDynamicGrid,
TextCustom,
ViewWrapper,
} from "@/components";
import { IconPlus } from "@/components/_Icon";
import { apiInvestmentGetNews } from "@/service/api-client/api-investment";
import {
router,
Stack,
useFocusEffect,
useLocalSearchParams,
} from "expo-router";
import _ from "lodash";
import { useCallback, useState } from "react";
import Investment_ScreenListOfNews from "@/screens/Invesment/News/ScreenListOfNews";
import { useLocalSearchParams } from "expo-router";
export default function InvestmentListOfNews() {
const { id } = useLocalSearchParams();
const [openDrawer, setOpenDrawer] = useState(false);
const [list, setList] = useState<any[] | null>(null);
const [loadList, setLoadList] = useState(false);
useFocusEffect(
useCallback(() => {
onLoadList();
}, [id])
);
const onLoadList = async () => {
try {
setLoadList(true);
const response = await apiInvestmentGetNews({
id: id as string,
category: "all-news",
});
setList(response.data);
} catch (error) {
console.log("[ERROR]", error);
} finally {
setLoadList(false);
}
};
return (
<>
<Stack.Screen
options={{
title: "Daftar Berita",
headerLeft: () => <BackButton />,
// headerRight: () => <DotButton onPress={() => setOpenDrawer(true)} />,
}}
/>
<ViewWrapper>
{loadList ? (
<LoaderCustom />
) : _.isEmpty(list) ? (
<TextCustom align="center" color="gray">
Tidak ada data
</TextCustom>
) : (
list?.map((item: any, index: number) => (
<BaseBox
key={index}
paddingBlock={5}
href={`/investment/[id]/(news)/${item.id}`}
>
<TextCustom bold>{item.title}</TextCustom>
</BaseBox>
))
)}
</ViewWrapper>
<DrawerCustom
isVisible={openDrawer}
closeDrawer={() => setOpenDrawer(false)}
height={"auto"}
>
<MenuDrawerDynamicGrid
data={[
{
label: "Tambah Berita",
path: `/investment/${id}/add-news`,
icon: <IconPlus />,
},
]}
onPressItem={(item) => {
router.push(item.path as any);
setOpenDrawer(false);
}}
/>
</DrawerCustom>
</>
<Investment_ScreenListOfNews investmentId={id as string} />
);
}

View File

@@ -1,101 +1,10 @@
/* eslint-disable react-hooks/exhaustive-deps */
import {
BackButton,
BaseBox,
DotButton,
DrawerCustom,
LoaderCustom,
MenuDrawerDynamicGrid,
TextCustom,
ViewWrapper,
} from "@/components";
import { IconPlus } from "@/components/_Icon";
import { apiInvestmentGetNews } from "@/service/api-client/api-investment";
import {
router,
Stack,
useFocusEffect,
useLocalSearchParams,
} from "expo-router";
import _ from "lodash";
import { useCallback, useState } from "react";
import Investment_ScreenRecapOfNews from "@/screens/Invesment/News/ScreenRecapOfNews";
import { useLocalSearchParams } from "expo-router";
export default function InvestmentRecapOfNews() {
const { id } = useLocalSearchParams();
const [openDrawer, setOpenDrawer] = useState(false);
const [list, setList] = useState<any[] | null>(null);
const [loadList, setLoadList] = useState(false);
useFocusEffect(
useCallback(() => {
onLoadList();
}, [id])
);
const onLoadList = async () => {
try {
setLoadList(true);
const response = await apiInvestmentGetNews({
id: id as string,
category: "all-news",
});
setList(response.data);
} catch (error) {
console.log("[ERROR]", error);
} finally {
setLoadList(false);
}
};
return (
<>
<Stack.Screen
options={{
title: "Rekap Berita",
headerLeft: () => <BackButton />,
headerRight: () => <DotButton onPress={() => setOpenDrawer(true)} />,
}}
/>
<ViewWrapper>
{loadList ? (
<LoaderCustom />
) : _.isEmpty(list) ? (
<TextCustom align="center" color="gray">
Tidak ada data
</TextCustom>
) : (
list?.map((item: any, index: number) => (
<BaseBox
key={index}
paddingBlock={5}
href={`/investment/[id]/(news)/${item.id}`}
>
<TextCustom bold>{item.title}</TextCustom>
</BaseBox>
))
)}
</ViewWrapper>
<DrawerCustom
isVisible={openDrawer}
closeDrawer={() => setOpenDrawer(false)}
height={"auto"}
>
<MenuDrawerDynamicGrid
data={[
{
label: "Tambah Berita",
path: `/investment/${id}/add-news`,
icon: <IconPlus />,
},
]}
onPressItem={(item) => {
router.push(item.path as any);
setOpenDrawer(false);
}}
/>
</DrawerCustom>
</>
<Investment_ScreenRecapOfNews investmentId={id as string} />
);
}

View File

@@ -1,239 +1,10 @@
/* eslint-disable react-hooks/exhaustive-deps */
import {
BaseBox,
ButtonCenteredOnly,
ButtonCustom,
Grid,
InformationBox,
Spacing,
StackCustom,
TextCustom,
ViewWrapper,
} from "@/components";
import CopyButton from "@/components/Button/CoyButton";
import { MainColor } from "@/constants/color-palet";
import DIRECTORY_ID from "@/constants/directory-id";
import {
apiInvestmentGetInvoice,
apiInvestmentUpdateInvoice,
} from "@/service/api-client/api-investment";
import { uploadFileService } from "@/service/upload-service";
import { formatCurrencyDisplay } from "@/utils/formatCurrencyDisplay";
import pickFile, { IFileData } from "@/utils/pickFile";
import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
import { useCallback, useState } from "react";
import { View } from "react-native";
import Toast from "react-native-toast-message";
import Investment_ScreenInvoice from "@/screens/Invesment/ScreenInvoice";
export default function InvestmentInvoice() {
const { id } = useLocalSearchParams();
console.log("[ID]", id);
const [data, setData] = useState<any>({});
const [image, setImage] = useState<IFileData>({
name: "",
uri: "",
size: 0,
});
const [isLoading, setIsLoading] = useState<boolean>(false);
useFocusEffect(
useCallback(() => {
onLoadData();
}, [id])
);
const onLoadData = async () => {
try {
const response = await apiInvestmentGetInvoice({
id: id as string,
category: "invoice",
});
console.log("[RES INVOICE]", JSON.stringify(response.data, null, 2));
setData(response.data);
} catch (error) {
console.log("[ERROR]", error);
}
};
const handlerSubmitUpdate = async () => {
try {
setIsLoading(true);
const responseUploadImage = await uploadFileService({
dirId: DIRECTORY_ID.investasi_bukti_transfer,
imageUri: image?.uri,
});
console.log("[RESPONSE UPLOAD IMAGE]", responseUploadImage);
if (!responseUploadImage?.data?.id) {
Toast.show({
type: "error",
text1: "Gagal mengunggah bukti transfer",
});
return;
}
const response = await apiInvestmentUpdateInvoice({
id: id as string,
data: {
imageId: responseUploadImage?.data?.id,
},
status: "proses",
});
if (response.success) {
console.log(
"[RESPONSE UPDATE]",
JSON.stringify(response.data, null, 2)
);
Toast.show({
type: "success",
text1: "Berhasil mengunggah bukti transfer",
});
router.push(`/investment/${id}/(transaction-flow)/process`);
}
} catch (error) {
console.log("[ERROR]", error);
} finally {
setIsLoading(false);
}
};
return (
<>
<ViewWrapper>
<StackCustom>
<InformationBox text="Mohon transfer ke rekening dibawah" />
<BaseBox>
<StackCustom gap={"xs"}>
<Grid>
<Grid.Col span={4}>
<TextCustom>Bank</TextCustom>
</Grid.Col>
<Grid.Col span={8}>
<TextCustom>{data?.MasterBank?.namaBank}</TextCustom>
</Grid.Col>
</Grid>
<Spacing height={10} />
<Grid>
<Grid.Col span={4}>
<TextCustom>Nama Akun</TextCustom>
</Grid.Col>
<Grid.Col span={8}>
<TextCustom>{data?.MasterBank?.namaAkun}</TextCustom>
</Grid.Col>
</Grid>
<BaseBox backgroundColor={MainColor.soft_darkblue}>
<Grid containerStyle={{ justifyContent: "center" }}>
<Grid.Col
span={8}
style={{
justifyContent: "center",
}}
>
<TextCustom size="xlarge" bold color="yellow">
{data?.MasterBank?.norek}
</TextCustom>
</Grid.Col>
<Grid.Col
span={4}
style={{
alignItems: "flex-end",
}}
>
<CopyButton textToCopy={data?.MasterBank?.norek} />
</Grid.Col>
</Grid>
</BaseBox>
</StackCustom>
</BaseBox>
<BaseBox>
<StackCustom gap={"xs"}>
<TextCustom>Jumlah Transaksi</TextCustom>
<Spacing height={10} />
<BaseBox backgroundColor={MainColor.soft_darkblue}>
<Grid containerStyle={{ justifyContent: "center" }}>
<Grid.Col
span={8}
style={{
justifyContent: "center",
}}
>
<TextCustom size="xlarge" bold color="yellow">
Rp. {formatCurrencyDisplay(data?.nominal)}
</TextCustom>
</Grid.Col>
<Grid.Col
span={4}
style={{
alignItems: "flex-end",
}}
>
<CopyButton textToCopy={data?.nominal} />
</Grid.Col>
</Grid>
</BaseBox>
</StackCustom>
</BaseBox>
<BaseBox>
<StackCustom>
<TextCustom align="center">
Upload bukti transfer anda.
</TextCustom>
{image ? (
<View
style={{
flexDirection: "row",
alignItems: "center",
justifyContent: "center",
gap: 10,
paddingInline: 20,
}}
>
<TextCustom bold align="center" truncate>
{image?.name}
</TextCustom>
</View>
) : (
<TextCustom align="center">
Tidak ada gambar yang diunggah
</TextCustom>
)}
<ButtonCenteredOnly
onPress={() => {
pickFile({
allowedType: "image",
setImageUri(file: any) {
console.log("[IMAGE]", file);
setImage(file);
},
});
}}
icon="upload"
>
Upload
</ButtonCenteredOnly>
</StackCustom>
</BaseBox>
<ButtonCustom
isLoading={isLoading}
disabled={!image}
onPress={() => {
handlerSubmitUpdate();
}}
>
Saya Sudah Transfer
</ButtonCustom>
</StackCustom>
<Spacing />
</ViewWrapper>
<Investment_ScreenInvoice />
</>
);
}

View File

@@ -56,7 +56,6 @@ export default function InvestmentSelectBank() {
});
if (response.success) {
console.log("[RESPONSE >>]", response);
const invoiceId = response.data.id;
const delStorage = await AsyncStorage.removeItem(

View File

@@ -73,7 +73,6 @@ export default function InvestmentDetailStatus() {
updateCountDown();
}, [data]);
console.log("[DATA DETAIL]", JSON.stringify(data, null, 2));
const updateCountDown = () => {
const countDown = countDownAndCondition({

View File

@@ -1,15 +1,16 @@
/* eslint-disable react-hooks/exhaustive-deps */
import {
BoxButtonOnFooter,
ButtonCenteredOnly,
ButtonCustom,
InformationBox,
LandscapeFrameUploaded,
LoaderCustom,
NewWrapper,
SelectCustom,
Spacing,
StackCustom,
TextInputCustom,
ViewWrapper,
TextInputCustom
} from "@/components";
import API_STRORAGE from "@/constants/base-url-api-strorage";
import DIRECTORY_ID from "@/constants/directory-id";
@@ -18,10 +19,7 @@ import {
apiInvestmentUpdateData,
} from "@/service/api-client/api-investment";
import { apiMasterInvestment } from "@/service/api-client/api-master";
import {
deleteFileService,
uploadFileService,
} from "@/service/upload-service";
import { deleteFileService, uploadFileService } from "@/service/upload-service";
import { formatCurrencyDisplay } from "@/utils/formatCurrencyDisplay";
import pickFile from "@/utils/pickFile";
import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
@@ -70,7 +68,7 @@ export default function InvestmentEdit() {
useCallback(() => {
onLoadMaster();
onLoadData();
}, [id])
}, [id]),
);
const onLoadMaster = async () => {
@@ -178,7 +176,7 @@ export default function InvestmentEdit() {
const responseUpdate = await apiInvestmentUpdateData({
id: id as string,
data: newData,
category: "data"
category: "data",
});
if (responseUpdate.success) {
@@ -201,7 +199,15 @@ export default function InvestmentEdit() {
};
return (
<ViewWrapper>
<NewWrapper
footerComponent={
<BoxButtonOnFooter>
<ButtonCustom isLoading={isLoading} onPress={handleSubmitUpdate}>
Simpan
</ButtonCustom>
</BoxButtonOnFooter>
}
>
<StackCustom gap={"xs"}>
<InformationBox text="Gambar investasi bisa berupa ilustrasi, poster atau foto terkait investasi." />
<LandscapeFrameUploaded
@@ -256,6 +262,8 @@ export default function InvestmentEdit() {
/>
<TextInputCustom
iconLeft="Rp."
// disabled
required
placeholder="0"
label="Total Lembar"
@@ -341,11 +349,7 @@ export default function InvestmentEdit() {
)}
<Spacing />
<ButtonCustom isLoading={isLoading} onPress={handleSubmitUpdate}>
Simpan
</ButtonCustom>
</StackCustom>
<Spacing height={50} />
</ViewWrapper>
</NewWrapper>
);
}

View File

@@ -72,7 +72,6 @@ export default function InvestmentDetail() {
updateCountDown();
}, [data]);
console.log("[DATA DETAIL]", JSON.stringify(data, null, 2));
const updateCountDown = () => {
const countDown = countDownAndCondition({

View File

@@ -1,67 +1,10 @@
/* 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";
import Investment_ScreenInvestor from "@/screens/Invesment/ScreenInvestor";
import { useLocalSearchParams } from "expo-router";
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>
{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>
</>
<Investment_ScreenInvestor investmentId={id as string} />
);
}

View File

@@ -1,18 +1,19 @@
import {
BaseBox,
BoxButtonOnFooter,
ButtonCenteredOnly,
ButtonCustom,
CenterCustom,
InformationBox,
LandscapeFrameUploaded,
LoaderCustom,
NewWrapper,
SelectCustom,
Spacing,
StackCustom,
TextCustom,
TextInputCustom,
ViewWrapper,
} from "@/components";
import CustomSkeleton from "@/components/_ShareComponent/SkeletonCustom";
import { MainColor } from "@/constants/color-palet";
import DIRECTORY_ID from "@/constants/directory-id";
import { useAuth } from "@/hooks/use-auth";
@@ -54,7 +55,7 @@ export default function InvestmentCreate() {
useFocusEffect(
useCallback(() => {
onLoadMaster();
}, [])
}, []),
);
const onLoadMaster = async () => {
@@ -167,7 +168,7 @@ export default function InvestmentCreate() {
text1: "Berhasil",
text2: response.message,
});
router.replace("/investment/portofolio");
router.replace("/investment/portofolio?status=review");
} else {
Toast.show({
type: "error",
@@ -184,7 +185,19 @@ export default function InvestmentCreate() {
// const [coba, setCoba] = useState("");
return (
<ViewWrapper>
<NewWrapper
footerComponent={
<BoxButtonOnFooter>
<ButtonCustom
disabled={isLoading}
isLoading={isLoading}
onPress={() => handleSubmit()}
>
Simpan
</ButtonCustom>
</BoxButtonOnFooter>
}
>
<StackCustom gap={"xs"}>
<InformationBox text="Gambar investasi bisa berupa ilustrasi, poster atau foto terkait investasi." />
<LandscapeFrameUploaded image={image as string} />
@@ -224,7 +237,6 @@ export default function InvestmentCreate() {
onPress={() => {
pickFile({
setPdfUri: ({ uri, name, size }) => {
setPdf({ uri, name, size });
},
allowedType: "pdf",
@@ -265,6 +277,9 @@ export default function InvestmentCreate() {
<StackCustom gap={0}>
<TextInputCustom
iconLeft="Rp."
// disabled
editable={false}
required
placeholder="0"
label="Total Lembar"
@@ -291,7 +306,7 @@ export default function InvestmentCreate() {
/>
{loadingMaster ? (
<LoaderCustom />
<CustomSkeleton height={50} />
) : (
<SelectCustom
required
@@ -313,7 +328,7 @@ export default function InvestmentCreate() {
)}
{loadingMaster ? (
<LoaderCustom />
<CustomSkeleton height={50} />
) : (
<SelectCustom
required
@@ -335,7 +350,7 @@ export default function InvestmentCreate() {
)}
{loadingMaster ? (
<LoaderCustom />
<CustomSkeleton height={50} />
) : (
<SelectCustom
required
@@ -357,11 +372,8 @@ export default function InvestmentCreate() {
)}
<Spacing />
<ButtonCustom isLoading={isLoading} onPress={() => handleSubmit()}>
Simpan
</ButtonCustom>
</StackCustom>
<Spacing height={50} />
</ViewWrapper>
{/* <Spacing height={50} /> */}
</NewWrapper>
);
}

View File

@@ -1,57 +1,10 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { BaseBox, LoaderCustom, TextCustom, ViewWrapper } from "@/components";
import { useAuth } from "@/hooks/use-auth";
import { apiJobGetAll } from "@/service/api-client/api-job";
import { useFocusEffect } from "expo-router";
import _ from "lodash";
import { useCallback, useState } from "react";
import Job_ScreenArchive2 from "@/screens/Job/ScreenArchive2";
export default function JobArchive() {
const { user } = useAuth();
const [listData, setListData] = useState<any[]>([]);
const [isLoadData, setIsLoadData] = useState(false);
useFocusEffect(
useCallback(() => {
onLoadData();
}, [user?.id])
);
const onLoadData = async () => {
try {
setIsLoadData(true);
const response = await apiJobGetAll({
category: "archive",
authorId: user?.id,
});
setListData(response.data);
} catch (error) {
console.log("[ERROR]", error);
} finally {
setIsLoadData(false);
}
};
return (
<ViewWrapper hideFooter>
{isLoadData ? (
<LoaderCustom />
) : _.isEmpty(listData) ? (
<TextCustom align="center">Anda tidak memiliki arsip</TextCustom>
) : (
listData.map((item, index) => (
<BaseBox
key={index}
paddingTop={20}
paddingBottom={20}
href={`/job/${item.id}/archive`}
>
<TextCustom align="center" bold truncate size="large">
{item?.title || "-"}
</TextCustom>
</BaseBox>
))
)}
</ViewWrapper>
<>
<Job_ScreenArchive2 />
</>
);
}

View File

@@ -1,83 +1,10 @@
import {
AvatarUsernameAndOtherComponent,
BoxWithHeaderSection,
FloatingButton,
LoaderCustom,
SearchInput,
Spacing,
StackCustom,
TextCustom,
ViewWrapper,
} from "@/components";
import { apiJobGetAll } from "@/service/api-client/api-job";
import { router, useFocusEffect } from "expo-router";
import _ from "lodash";
import { useCallback, useState } from "react";
import Job_ScreenBeranda from "@/screens/Job/ScreenBeranda";
import Job_ScreenBeranda2 from "@/screens/Job/ScreenBeranda2";
export default function JobBeranda() {
const [listData, setListData] = useState<any[]>([]);
const [isLoadData, setIsLoadData] = useState(false);
const [search, setSearch] = useState("");
useFocusEffect(
useCallback(() => {
onLoadData(search);
}, [search])
);
const onLoadData = async (search: string) => {
try {
setIsLoadData(true);
const response = await apiJobGetAll({ search, category: "beranda" });
setListData(response.data);
} catch (error) {
console.log("[ERROR]", error);
} finally {
setIsLoadData(false);
}
};
const handleSearch = (search: string) => {
setSearch(search);
onLoadData(search);
};
return (
<ViewWrapper
hideFooter
floatingButton={
<FloatingButton onPress={() => router.push("/job/create")} />
}
headerComponent={
<SearchInput placeholder="Cari pekerjaan" onChangeText={handleSearch} />
}
>
{isLoadData ? (
<LoaderCustom />
) : _.isEmpty(listData) ? (
<TextCustom align="center">Belum ada lowongan</TextCustom>
) : (
listData.map((item, index) => (
<BoxWithHeaderSection
key={index}
onPress={() => router.push(`/job/${item.id}`)}
>
<StackCustom>
<AvatarUsernameAndOtherComponent
avatar={item?.Author?.Profile?.imageId}
avatarHref={`/profile/${item?.Author?.Profile?.id}`}
name={item?.Author?.username}
/>
<TextCustom truncate={2} align="center" bold size="large">
{item?.title || "-"}
</TextCustom>
</StackCustom>
<Spacing />
</BoxWithHeaderSection>
))
)}
<Spacing />
</ViewWrapper>
<>
<Job_ScreenBeranda2 />
</>
);
}

View File

@@ -1,91 +1,12 @@
/* eslint-disable react-hooks/exhaustive-deps */
import {
BaseBox,
LoaderCustom,
ScrollableCustom,
TextCustom,
ViewWrapper,
} from "@/components";
import { useAuth } from "@/hooks/use-auth";
import { dummyMasterStatus } from "@/lib/dummy-data/_master/status";
import { apiJobGetByStatus } from "@/service/api-client/api-job";
import { useFocusEffect, useLocalSearchParams } from "expo-router";
import _ from "lodash";
import { useCallback, useState } from "react";
import Job_MainViewStatus from "@/screens/Job/MainViewStatus";
import Job_MainViewStatus2 from "@/screens/Job/MainViewStatus2";
export default function JobStatus() {
const { user } = useAuth();
const { status } = useLocalSearchParams<{ status?: string }>();
console.log("STATUS", status);
const [activeCategory, setActiveCategory] = useState<string | null>(
status || "publish"
);
const [listData, setListData] = useState<any[]>([]);
const [isLoadList, setIsLoadList] = useState(false);
useFocusEffect(
useCallback(() => {
onLoadData();
}, [user?.id, activeCategory])
);
const onLoadData = async () => {
try {
setIsLoadList(true);
const response = await apiJobGetByStatus({
authorId: user?.id as string,
status: activeCategory as string,
});
setListData(response.data);
} catch (error) {
console.log("[ERROR]", error);
} finally {
setIsLoadList(false);
}
};
const handlePress = (item: any) => {
setActiveCategory(item.value);
// tambahkan logika lain seperti filter dsb.
};
const scrollComponent = (
<ScrollableCustom
data={dummyMasterStatus.map((e, i) => ({
id: i,
label: e.label,
value: e.value,
}))}
onButtonPress={handlePress}
activeId={activeCategory as any}
/>
);
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>
{/* <Job_MainViewStatus /> */}
<Job_MainViewStatus2 />
</>
);
}

View File

@@ -1,13 +1,14 @@
import {
BoxButtonOnFooter,
ButtonCenteredOnly,
ButtonCustom,
InformationBox,
LandscapeFrameUploaded,
NewWrapper,
Spacing,
StackCustom,
TextAreaCustom,
TextInputCustom,
ViewWrapper
TextInputCustom
} from "@/components";
import DIRECTORY_ID from "@/constants/directory-id";
import { useAuth } from "@/hooks/use-auth";
@@ -99,16 +100,17 @@ export default function JobCreate() {
const buttonSubmit = () => {
return (
<>
<ButtonCustom isLoading={isLoading} onPress={() => handlerOnSubmit()}>
Simpan
</ButtonCustom>
<Spacing />
<BoxButtonOnFooter>
<ButtonCustom isLoading={isLoading} onPress={() => handlerOnSubmit()}>
Simpan
</ButtonCustom>
</BoxButtonOnFooter>
</>
);
};
return (
<ViewWrapper>
<NewWrapper footerComponent={buttonSubmit()}>
<StackCustom gap={"xs"}>
<InformationBox text="Poster atau gambar lowongan kerja bersifat opsional, tidak wajib untuk dimasukkan dan upload lah gambar yang sesuai dengan deskripsi lowongan kerja." />
@@ -160,9 +162,7 @@ export default function JobCreate() {
value={data.deskripsi}
onChangeText={(value) => setData({ ...data, deskripsi: value })}
/>
{buttonSubmit()}
</StackCustom>
</ViewWrapper>
</NewWrapper>
);
}

View File

@@ -1,244 +1,5 @@
/* eslint-disable react-hooks/exhaustive-deps */
import {
BoxButtonOnFooter,
ButtonCenteredOnly,
ButtonCustom,
InformationBox,
LandscapeFrameUploaded,
Spacing,
TextInputCustom,
ViewWrapper,
} from "@/components";
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";
import { Maps_ScreenMapsEdit } from "@/screens/Maps/ScreenMapsEdit";
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
disabled={!data.namePin}
onPress={handleSubmit}
isLoading={isLoading}
>
Update
</ButtonCustom>
</BoxButtonOnFooter>
);
return (
<ViewWrapper footerComponent={buttonFooter}>
<InformationBox text="Tentukan lokasi pin map dengan menekan pada map." />
<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
image={
image
? image?.uri
: API_IMAGE.GET({ fileId: data?.imageId as string })
}
/>
<ButtonCenteredOnly
icon="upload"
onPress={() => {
pickFile({
allowedType: "image",
setImageUri(file) {
setImage(file);
},
});
}}
>
Upload
</ButtonCenteredOnly>
<Spacing height={50} />
</ViewWrapper>
);
return <Maps_ScreenMapsEdit />;
}
const styles = StyleSheet.create({
container: {
width: "100%",
backgroundColor: "#f5f5f5",
overflow: "hidden",
borderRadius: 8,
marginBottom: 20,
},
map: {
flex: 1,
},
});

View File

@@ -1,143 +1,9 @@
import {
BaseBox,
BoxButtonOnFooter,
ButtonCenteredOnly,
ButtonCustom,
InformationBox,
LandscapeFrameUploaded,
Spacing,
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";
import Maps_ScreenMapsCreate from "@/screens/Maps/ScreenMapsCreate";
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
isLoading={isLoading}
disabled={!selectedLocation || name === ""}
onPress={handleSubmit}
>
Simpan
</ButtonCustom>
</BoxButtonOnFooter>
);
return (
<ViewWrapper footerComponent={buttonFooter}>
<InformationBox text="Tentukan lokasi pin map dengan menekan pada map." />
<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 image={image?.uri} />
<ButtonCenteredOnly
icon="upload"
onPress={() => {
pickFile({
allowedType: "image",
setImageUri(file) {
setImage(file);
},
});
}}
>
Upload
</ButtonCenteredOnly>
<Spacing height={50} />
</ViewWrapper>
<>
<Maps_ScreenMapsCreate />
</>
);
}

View File

@@ -1,6 +1,4 @@
import MapsView from "@/screens/Maps/MapsView";
import MapsView2 from "@/screens/Maps/MapsView2";
import { Text, View } from "react-native";
export interface LocationItem {
id: string | number;
@@ -13,8 +11,14 @@ export interface LocationItem {
export default function Maps() {
return (
<>
<MapsView />
{/* <MapsView2 />, */}
{/* <Stack.Screen
options={{
title: "Maps",
headerLeft: () => <BackButton />,
}}
/> */}
{/* {Platform.OS === "ios" ? <MapsView /> : <MapsView2 />} */}
<MapsView2 />
{/* <View style={{ flex: 1, backgroundColor: "gray" }}><Text style={{ color: "white" }}>Map disabled</Text></View> */}
</>
);

View File

@@ -1,248 +1,11 @@
/* eslint-disable react-hooks/exhaustive-deps */
import {
AlertDefaultSystem,
BackButton,
BaseBox,
DrawerCustom,
MenuDrawerDynamicGrid,
NewWrapper,
ScrollableCustom,
StackCustom,
TextCustom,
} from "@/components";
import { IconDot } from "@/components/_Icon/IconComponent";
import ListSkeletonComponent from "@/components/_ShareComponent/ListSkeletonComponent";
import NoDataText from "@/components/_ShareComponent/NoDataText";
import { AccentColor, MainColor } from "@/constants/color-palet";
import { ICON_SIZE_SMALL } from "@/constants/constans-value";
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 { Ionicons } from "@expo/vector-icons";
import { router, Stack, useFocusEffect, useLocalSearchParams } from "expo-router";
import _ from "lodash";
import { useCallback, useState } from "react";
import { RefreshControl, View } from "react-native";
import ScreenNotification_V1 from "@/screens/Notification/ScreenNotification_V1";
import ScreenNotification_V2 from "@/screens/Notification/ScreenNotification_V2";
const selectedCategory = (value: string) => {
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 = ({
data,
activeCategory,
}: {
data: any;
activeCategory: string | null;
}) => {
// console.log("DATA NOTIFICATION", JSON.stringify(data, null, 2));
const { markAsRead } = useNotificationStore();
export default function Notification() {
return (
<>
<BaseBox
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.navigate(newPath as any);
selectedCategory(activeCategory as string);
if (!data.isRead) {
markAsRead(data.id);
}
}}
>
<StackCustom>
<TextCustom truncate={2} bold>
{data.title}
</TextCustom>
<TextCustom truncate={2}>{data.pesan}</TextCustom>
<TextCustom size="small" color="gray">
{formatChatTime(data.createdAt)}
</TextCustom>
</StackCustom>
</BaseBox>
</>
);
};
export default function Notifications() {
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 [openDrawer, setOpenDrawer] = useState(false);
const { markAsReadAll } = useNotificationStore();
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 (
<>
<Stack.Screen
options={{
title: "Notifikasi",
headerLeft: () => <BackButton />,
headerRight: () => (
<IconDot
color={MainColor.yellow}
onPress={() => setOpenDrawer(true)}
/>
),
}}
/>
<NewWrapper
headerComponent={
<ScrollableCustom
data={listOfcategoriesAppNotification.map((e, i) => ({
id: i,
label: e.label,
value: e.value,
}))}
onButtonPress={handlePress}
activeId={activeCategory as string}
/>
}
refreshControl={
<RefreshControl refreshing={refreshing} onRefresh={onRefresh} />
}
>
{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>
<DrawerCustom
isVisible={openDrawer}
closeDrawer={() => setOpenDrawer(false)}
height={"auto"}
>
<MenuDrawerDynamicGrid
data={[
{
label: "Tandai Semua Dibaca",
value: "read-all",
icon: (
<Ionicons
name="reader-outline"
size={ICON_SIZE_SMALL}
color={MainColor.white}
/>
),
path: "",
},
]}
onPressItem={(item: any) => {
console.log("Item", item.value);
if (item.value === "read-all") {
AlertDefaultSystem({
title: "Tandai Semua Dibaca",
message:
"Apakah Anda yakin ingin menandai semua notifikasi dibaca?",
textLeft: "Batal",
textRight: "Ya",
onPressRight: () => {
markAsReadAll(user?.id as any);
const data = _.cloneDeep(listData);
data.forEach((e) => {
e.isRead = true;
});
setListData(data);
onRefresh();
setOpenDrawer(false);
},
});
}
}}
/>
</DrawerCustom>
<ScreenNotification_V2 />
{/* <ScreenNotification_V1 /> */}
</>
);
}

View File

@@ -1,364 +1,5 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import {
ActionIcon,
AvatarComp,
BaseBox,
ButtonCenteredOnly,
CenterCustom,
Grid,
InformationBox,
SelectCustom,
Spacing,
StackCustom,
TextAreaCustom,
TextCustom,
TextInputCustom,
ViewWrapper,
} from "@/components";
import { IconPlus } from "@/components/_Icon";
import { MainColor } from "@/constants/color-palet";
import { ICON_SIZE_XLARGE } from "@/constants/constans-value";
import DUMMY_IMAGE from "@/constants/dummy-image-value";
import Portofolio_ButtonCreate from "@/screens/Portofolio/ButtonCreatePortofolio";
import {
apiMasterBidangBisnis,
apiMasterSubBidangBisnis,
} from "@/service/api-client/api-master";
import {
IMasterBidangBisnis,
IMasterSubBidangBisnis,
} from "@/types/Type-Master";
import pickImage from "@/utils/pickImage";
import { Ionicons } from "@expo/vector-icons";
import { Image } from "expo-image";
import { useFocusEffect, useLocalSearchParams } from "expo-router";
import _ from "lodash";
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";
import { Admin_ScreenPortofolioCreate } from "@/screens/Portofolio/ScreenPortofolioCreate";
export default function PortofolioCreate() {
const { id } = useLocalSearchParams();
const [selectedCountry, setSelectedCountry] = useState<null | ICountry>(null);
const [inputValue, setInputValue] = useState<string>("");
const [data, setData] = useState({
namaBisnis: "",
masterBidangBisnisId: "",
alamatKantor: "",
tlpn: "",
deskripsi: "",
});
const [imageUri, setImageUri] = useState<string | null>(null);
const [bidangBisnis, setBidangBisnis] = useState<IMasterBidangBisnis[]>([]);
const [subBidangBisnis, setSubBidangBisnis] = useState<
IMasterSubBidangBisnis[]
>([]);
const [selectedSubBidang, setSelectedSubBidang] = useState<string[]>([]);
const [listSubBidangSelected, setListSubBidangSelected] = useState([
{
id: "",
},
]);
const [dataMedsos, setDataMedsos] = useState({
facebook: "",
twitter: "",
instagram: "",
youtube: "",
tiktok: "",
});
const [isLoadingCreate, setIsLoadingCreate] = useState(false);
function handleInputValue(phoneNumber: string) {
setInputValue(phoneNumber);
const callingCode = selectedCountry?.callingCode.replace(/^\+/, "") || "";
let fixNumber = inputValue.replace(/\s+/g, "").replace(/^0+/, "");
const realNumber = callingCode + fixNumber;
setData({ ...data, tlpn: realNumber });
}
function handleSelectedCountry(country: ICountry) {
setSelectedCountry(country);
}
useFocusEffect(
useCallback(() => {
onLoadMaster();
onLoadMasterSubBidangBisnis();
}, [])
);
const onLoadMaster = async () => {
try {
const response = await apiMasterBidangBisnis();
setBidangBisnis(response.data);
} catch (error) {
setBidangBisnis([]);
console.log("Error onLoadMasterBidangBisnis", error);
}
};
const onLoadMasterSubBidangBisnis = async () => {
try {
const response = await apiMasterSubBidangBisnis({});
setSubBidangBisnis(response.data);
} catch (error) {
setSubBidangBisnis([]);
console.log("Error onLoadMasterBidangBisnis", error);
}
};
const handlerSelectedSubBidang = ({ id }: { id: string }) => {
const selectedList = subBidangBisnis?.filter(
(item) => (item?.masterBidangBisnisId as any) === id
);
setSelectedSubBidang(selectedList as any[]);
};
return (
<ViewWrapper
footerComponent={
<Portofolio_ButtonCreate
id={id as string}
data={data}
dataMedsos={dataMedsos}
imageUri={imageUri}
subBidangSelected={listSubBidangSelected}
isLoadingCreate={isLoadingCreate}
setIsLoadingCreate={setIsLoadingCreate}
/>
}
>
{/* <TextCustom>Portofolio Create {id}</TextCustom> */}
<StackCustom gap={"xs"}>
<InformationBox text="Lengkapi data bisnis anda." />
<TextInputCustom
required
label="Nama Bisnis"
placeholder="Masukkan nama bisnis"
onChangeText={(value: any) => setData({ ...data, namaBisnis: value })}
/>
<SelectCustom
label="Bidang Usaha"
required
data={bidangBisnis.map((item) => ({
label: item.name,
value: item.id,
}))}
value={data.masterBidangBisnisId}
onChange={(value) => {
const isSameBidang = data.masterBidangBisnisId === value;
if (!isSameBidang) {
setListSubBidangSelected([{ id: "" }]);
}
setData({ ...(data as any), masterBidangBisnisId: value });
handlerSelectedSubBidang({ id: value as string });
}}
/>
{listSubBidangSelected.map((item, index) => (
<SelectCustom
key={index}
disabled={data.masterBidangBisnisId === ""}
label="Sub Bidang Usaha"
required
data={_.map(selectedSubBidang as any)
.filter((option: any) => {
const selectedValues = listSubBidangSelected.map((s) => s.id);
return (
option.id === item.id || // biarkan tetap muncul kalau ini valuenya sendiri
!selectedValues.includes(option.id)
);
})
.map((e: any) => ({
value: e.id,
label: e.name,
}))}
value={item.id || null}
onChange={(value) => {
const list = _.clone(listSubBidangSelected);
list[index].id = value as any;
setListSubBidangSelected(list);
}}
/>
))}
<CenterCustom>
<View style={{ flexDirection: "row", alignItems: "center", gap: 10 }}>
<ActionIcon
disabled={
selectedSubBidang.length === listSubBidangSelected.length
}
onPress={() => {
setListSubBidangSelected([
...listSubBidangSelected,
{ id: "" },
]);
}}
icon={
<Ionicons
name="add-circle-outline"
size={ICON_SIZE_XLARGE}
color={MainColor.black}
/>
}
size="xl"
/>
<ActionIcon
disabled={listSubBidangSelected.length <= 1}
onPress={() => {
const list = _.clone(listSubBidangSelected);
list.pop();
setListSubBidangSelected(list);
}}
icon={
<Ionicons
name="remove-circle-outline"
size={ICON_SIZE_XLARGE}
color={MainColor.black}
/>
}
size="xl"
/>
</View>
</CenterCustom>
<Spacing />
{/* <SelectCustom
label="Bidang Usaha"
required
data={bidangBisnis.map((item) => ({
label: item.name,
value: item.id,
}))}
value={null}
onChange={(value) => {
setData({ ...(data as any), masterBidangBisnisId: value });
handlerSelectedSubBidang({ id: value as string });
}}
/> */}
{/* <ButtonCenteredOnly
onPress={() => {
setListSubBidangSelected([...listSubBidangSelected, { id: "" }]);
}}
>
Tambah Pilihan
</ButtonCenteredOnly>
<Spacing /> */}
{/* <TextCustom>{JSON.stringify(bidangBisnis, null, 2)}</TextCustom> */}
<View>
<View style={{ flexDirection: "row", alignItems: "center" }}>
<TextCustom semiBold style={{ color: MainColor.white_gray }}>
Nomor Telepon
</TextCustom>
<Text style={{ color: "red" }}> *</Text>
</View>
<Spacing height={5} />
<PhoneInput
value={inputValue}
onChangePhoneNumber={handleInputValue}
selectedCountry={selectedCountry}
onChangeSelectedCountry={handleSelectedCountry}
defaultCountry="ID"
placeholder="xxx-xxx-xxx"
/>
</View>
<Spacing />
<TextInputCustom
required
label="Alamat Bisnis"
placeholder="Masukkan alamat bisnis"
onChangeText={(value: any) =>
setData({ ...data, alamatKantor: value })
}
/>
<TextAreaCustom
label="Deskripsi Bisnis"
placeholder="Masukkan deskripsi bisnis"
value={data.deskripsi}
onChangeText={(value: any) => setData({ ...data, deskripsi: value })}
autosize
minRows={2}
maxRows={5}
required
showCount
maxLength={1000}
/>
<Spacing />
{/* Logo */}
<InformationBox text="Upload logo bisnis anda untuk di tampilaka pada portofolio." />
<CenterCustom>
<Avatar.Image
source={imageUri ? { uri: imageUri } : DUMMY_IMAGE.dummy_image}
size={200}
/>
</CenterCustom>
<Spacing />
<ButtonCenteredOnly
icon="upload"
onPress={() => {
pickImage({
setImageUri,
});
}}
>
Upload
</ButtonCenteredOnly>
<Spacing height={40} />
{/* Social Media */}
<InformationBox text="Isi hanya pada sosial media yang anda miliki." />
<TextInputCustom
label="Tiktok"
placeholder="Masukkan username tiktok"
onChangeText={(value: any) =>
setDataMedsos({ ...dataMedsos, tiktok: value })
}
/>
<TextInputCustom
label="Facebook"
placeholder="Masukkan username facebook"
onChangeText={(value: any) =>
setDataMedsos({ ...dataMedsos, facebook: value })
}
/>
<TextInputCustom
label="Instagram"
placeholder="Masukkan username instagram"
onChangeText={(value: any) =>
setDataMedsos({ ...dataMedsos, instagram: value })
}
/>
<TextInputCustom
label="Twitter"
placeholder="Masukkan username twitter"
onChangeText={(value: any) =>
setDataMedsos({ ...dataMedsos, twitter: value })
}
/>
<TextInputCustom
label="Youtube"
placeholder="Masukkan username youtube"
onChangeText={(value: any) =>
setDataMedsos({ ...dataMedsos, youtube: value })
}
/>
<Spacing />
</StackCustom>
</ViewWrapper>
);
return <Admin_ScreenPortofolioCreate />;
}

View File

@@ -4,14 +4,15 @@ import {
BoxButtonOnFooter,
ButtonCustom,
CenterCustom,
NewWrapper,
SelectCustom,
Spacing,
StackCustom,
TextAreaCustom,
TextCustom,
TextInputCustom,
ViewWrapper,
} from "@/components";
import ListSkeletonComponent from "@/components/_ShareComponent/ListSkeletonComponent";
import { MainColor } from "@/constants/color-palet";
import { ICON_SIZE_XLARGE } from "@/constants/constans-value";
import {
@@ -238,7 +239,7 @@ export default function PortofolioEdit() {
return !dataArray.some(
(item: any) =>
!item.MasterSubBidangBisnis.id ||
item.MasterSubBidangBisnis.id.trim() === ""
item.MasterSubBidangBisnis.id.trim() === "",
);
}
@@ -319,16 +320,16 @@ export default function PortofolioEdit() {
if (!bidangBisnis || !subBidangBisnis) {
return (
<>
<ViewWrapper>
<ActivityIndicator size="large" color={MainColor.yellow} />
</ViewWrapper>
<NewWrapper>
<ListSkeletonComponent height={80} />
</NewWrapper>
</>
);
}
return (
<>
<ViewWrapper footerComponent={buttonUpdate}>
<NewWrapper footerComponent={buttonUpdate}>
<StackCustom gap={"xs"}>
<TextInputCustom
required
@@ -471,7 +472,7 @@ export default function PortofolioEdit() {
/>
<Spacing />
</StackCustom>
</ViewWrapper>
</NewWrapper>
</>
);
}

View File

@@ -10,6 +10,7 @@ import {
} from "@/components";
import LeftButtonCustom from "@/components/Button/BackButton";
import GridTwoView from "@/components/_ShareComponent/GridTwoView";
import CustomSkeleton from "@/components/_ShareComponent/SkeletonCustom";
import ViewWrapper from "@/components/_ShareComponent/ViewWrapper";
import { MainColor } from "@/constants/color-palet";
import { ICON_SIZE_SMALL } from "@/constants/constans-value";
@@ -49,7 +50,7 @@ export default function Portofolio() {
useCallback(() => {
onLoadData(id as string);
onLoadUserByToken();
}, [id])
}, [id]),
);
async function onLoadData(id: string) {
@@ -64,6 +65,8 @@ export default function Portofolio() {
setProfileId(response?.data?.Profile?.id);
};
return (
<>
{/* Header */}
@@ -87,18 +90,24 @@ export default function Portofolio() {
/>
<ViewWrapper>
{!data || !profileId ? (
<LoaderCustom />
<StackCustom>
<CustomSkeleton height={400} />
<CustomSkeleton height={300} />
</StackCustom>
) : (
<StackCustom>
<Portofolio_Data
data={data}
listSubBidang={data?.Portofolio_BidangDanSubBidangBisnis as any[]}
/>
<Portofolio_BusinessLocation
data={data?.BusinessMaps}
imageId={data?.logoId}
setOpenDrawerLocation={setOpenDrawerLocation}
/>
{data?.BusinessMaps && (
<Portofolio_BusinessLocation
data={data?.BusinessMaps}
imageId={data?.logoId}
setOpenDrawerLocation={setOpenDrawerLocation}
/>
)}
<Portofolio_SocialMediaSection
data={data?.Portofolio_MediaSosial}
/>
@@ -135,36 +144,38 @@ export default function Portofolio() {
closeDrawer={() => setOpenDrawerLocation(false)}
height={"auto"}
>
<DummyLandscapeImage
height={200}
imageId={data?.BusinessMaps?.imageId}
/>
{data?.BusinessMaps?.imageId && (
<DummyLandscapeImage
height={200}
imageId={data?.BusinessMaps?.imageId}
/>
)}
<Spacing />
<StackCustom gap={"xs"}>
<GridTwoView
spanLeft={2}
spanRight={10}
leftIcon={
leftItem={
<FontAwesome
name="building-o"
size={ICON_SIZE_SMALL}
color="white"
/>
}
rightIcon={<TextCustom>{data?.BusinessMaps?.namePin}</TextCustom>}
rightItem={<TextCustom>{data?.BusinessMaps?.namePin}</TextCustom>}
/>
<GridTwoView
spanLeft={2}
spanRight={10}
leftIcon={
leftItem={
<Ionicons
name="list-outline"
size={ICON_SIZE_SMALL}
color="white"
/>
}
rightIcon={
rightItem={
<TextCustom>{data?.MasterBidangBisnis?.name}</TextCustom>
}
/>
@@ -172,26 +183,26 @@ export default function Portofolio() {
<GridTwoView
spanLeft={2}
spanRight={10}
leftIcon={
leftItem={
<Ionicons
name="call-outline"
size={ICON_SIZE_SMALL}
color="white"
/>
}
rightIcon={<TextCustom>{data?.tlpn}</TextCustom>}
rightItem={<TextCustom>{data?.tlpn}</TextCustom>}
/>
<GridTwoView
spanLeft={2}
spanRight={10}
leftIcon={
leftItem={
<Ionicons
name="location-outline"
size={ICON_SIZE_SMALL}
color="white"
/>
}
rightIcon={<TextCustom>{data?.alamatKantor}</TextCustom>}
rightItem={<TextCustom>{data?.alamatKantor}</TextCustom>}
/>
<Spacing />

View File

@@ -1,28 +1,9 @@
import { TextCustom, ViewWrapper } from "@/components";
import Portofolio_BoxView from "@/screens/Portofolio/BoxPortofolioView";
import { apiGetPortofolio } from "@/service/api-client/api-portofolio";
import { useFocusEffect, useLocalSearchParams } from "expo-router";
import { useCallback, useState } from "react";
import ViewListPortofolio from "@/screens/Portofolio/ViewListPortofolio";
export default function ListPortofolio() {
const { id } = useLocalSearchParams();
const [data, setData] = useState<any[]>([]);
useFocusEffect(
useCallback(() => {
onLoadPortofolio(id as string);
}, [id])
);
const onLoadPortofolio = async (id: string) => {
const response = await apiGetPortofolio({ id: id });
setData(response.data);
};
return (
<ViewWrapper>
{data ? data?.map((item: any, index: number) => (
<Portofolio_BoxView key={index} data={item} />
)) : <TextCustom>Tidak ada portofolio</TextCustom>}
</ViewWrapper>
<>
<ViewListPortofolio />
</>
);
}

View File

@@ -29,7 +29,6 @@ export default function ProfileDetailBlocked() {
const fetchData = async () => {
const response = await apiGetBlockedById({ id: String(id) });
// console.log("[RESPONSE >>]", JSON.stringify(response, null, 2));
setData(response.data);
};

View File

@@ -1,6 +1,6 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { LoaderCustom } from "@/components";
import ViewWrapper from "@/components/_ShareComponent/ViewWrapper";
import { NewWrapper, StackCustom } from "@/components";
import CustomSkeleton from "@/components/_ShareComponent/SkeletonCustom";
import LeftButtonCustom from "@/components/Button/BackButton";
import DrawerCustom from "@/components/Drawer/DrawerCustom";
import { MainColor } from "@/constants/color-palet";
@@ -16,8 +16,8 @@ import { GStyles } from "@/styles/global-styles";
import { IProfile } from "@/types/Type-Profile";
import { Ionicons } from "@expo/vector-icons";
import { Stack, useFocusEffect, useLocalSearchParams } from "expo-router";
import React, { useCallback, useState } from "react";
import { TouchableOpacity } from "react-native";
import { useCallback, useState } from "react";
import { RefreshControl, TouchableOpacity } from "react-native";
export default function Profile() {
const { id } = useLocalSearchParams();
@@ -25,6 +25,7 @@ export default function Profile() {
const [data, setData] = useState<IProfile>();
const [dataToken, setDataToken] = useState<IProfile>();
const [listPortofolio, setListPortofolio] = useState<any[]>();
const [refreshing, setRefreshing] = useState(false);
const { token, logout, isAdmin, user, userData } = useAuth();
@@ -43,7 +44,7 @@ export default function Profile() {
onLoadUserByToken();
isUserCheck();
userData(token as string);
}, [id, token])
}, [id, token]),
);
const isUserCheck = () => {
@@ -54,13 +55,21 @@ export default function Profile() {
};
const onLoadData = async (id: string) => {
const response = await apiProfile({ id: id });
setData(response.data);
try {
const response = await apiProfile({ id: id });
setData(response.data);
} catch (error) {
console.log("[ERROR onLoadData]", error);
}
};
const onLoadUserByToken = async () => {
const response = await apiUser(user?.id as string);
setDataToken(response?.data?.Profile);
try {
const response = await apiUser(user?.id as string);
setDataToken(response?.data?.Profile);
} catch (error) {
console.log("[ERROR onLoadUserByToken]", error);
}
};
const onLoadPortofolio = async (id: string) => {
@@ -69,15 +78,25 @@ export default function Profile() {
const lastTwoByDate = response.data
.sort(
(a: any, b: any) =>
new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()
new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime(),
) // urut desc
.slice(0, 2);
setListPortofolio(lastTwoByDate);
} catch (error) {
console.log("[ERROR]", error);
console.log("[ERROR onLoadPortofolio]", error);
}
};
const onRefresh = useCallback(() => {
setRefreshing(true);
onLoadData(id as string);
onLoadPortofolio(id as string);
onLoadUserByToken();
isUserCheck();
userData(token as string);
setRefreshing(false);
}, [id, token]);
return (
<>
<Stack.Screen
@@ -97,9 +116,21 @@ export default function Profile() {
}}
/>
{/* Main View */}
<ViewWrapper>
<NewWrapper
refreshControl={
<RefreshControl
refreshing={refreshing}
onRefresh={onRefresh}
tintColor={MainColor.yellow}
colors={[MainColor.yellow]}
/>
}
>
{!data || !dataToken ? (
<LoaderCustom />
<StackCustom>
<CustomSkeleton height={400} />
<CustomSkeleton height={200} />
</StackCustom>
) : (
<>
<ProfileSection data={data as any} />
@@ -110,7 +141,7 @@ export default function Profile() {
/>
</>
)}
</ViewWrapper>
</NewWrapper>
{/* Drawer Komponen Eksternal */}
<DrawerCustom

View File

@@ -36,7 +36,7 @@ export default function ProfileLayout() {
<Stack.Screen
name="[id]/blocked-list"
options={{ title: "Blocked List", headerLeft: () => <BackButton /> }}
options={{ title: "Daftar Blokir", headerLeft: () => <BackButton /> }}
/>
<Stack.Screen

View File

@@ -1,115 +1,11 @@
import {
AvatarComp,
ClickableCustom,
Grid,
LoaderCustom,
Spacing,
StackCustom,
TextCustom,
TextInputCustom,
ViewWrapper,
} from "@/components";
import { MainColor } from "@/constants/color-palet";
import { ICON_SIZE_SMALL } from "@/constants/constans-value";
import { apiAllUser } from "@/service/api-client/api-user";
import { Ionicons } from "@expo/vector-icons";
import { router } from "expo-router";
import _ from "lodash";
import { useEffect, useState } from "react";
import UserSearchMainView from "@/screens/UserSeach/MainView";
import UserSearchMainView_V2 from "@/screens/UserSeach/MainView_V2";
export default function UserSearch() {
const [data, setData] = useState<any[]>([]);
const [search, setSearch] = useState<string>("");
const [isLoadList, setIsLoadList] = useState(false);
useEffect(() => {
onLoadData(search);
}, [search]);
const onLoadData = async (search: string) => {
try {
setIsLoadList(true);
const response = await apiAllUser({ search: search });
console.log("[DATA USER] >", JSON.stringify(response.data, null, 2));
setData(response.data);
} catch (error) {
console.log("Error fetching data", error);
} finally {
setIsLoadList(false);
}
};
const handleSearch = (search: string) => {
setSearch(search);
onLoadData(search);
};
return (
<>
<ViewWrapper
headerComponent={
<TextInputCustom
value={search}
onChangeText={handleSearch}
iconLeft={
<Ionicons
name="search"
size={ICON_SIZE_SMALL}
color={MainColor.placeholder}
/>
}
placeholder="Cari Pengguna"
borderRadius={50}
containerStyle={{ marginBottom: 0 }}
/>
}
>
<StackCustom>
{isLoadList ? (
<LoaderCustom />
) : !_.isEmpty(data) ? (
data?.map((e, index) => {
return (
<ClickableCustom
key={index}
onPress={() => {
console.log("Ke Profile");
router.push(`/profile/${e?.Profile?.id}`);
}}
>
<Grid>
<Grid.Col span={2}>
<AvatarComp fileId={e?.Profile?.imageId} size="base" />
</Grid.Col>
<Grid.Col span={9}>
<StackCustom gap={"sm"}>
<TextCustom size="large">{e?.username}</TextCustom>
<TextCustom size="small">+{e?.nomor}</TextCustom>
</StackCustom>
</Grid.Col>
<Grid.Col
span={1}
style={{
justifyContent: "center",
alignItems: "flex-end",
}}
>
<Ionicons
name="chevron-forward"
size={ICON_SIZE_SMALL}
color={MainColor.white}
/>
</Grid.Col>
</Grid>
</ClickableCustom>
);
})
) : (
<TextCustom align="center">Tidak ditemukan</TextCustom>
)}
</StackCustom>
<Spacing height={50} />
</ViewWrapper>
{/* <UserSearchMainView /> */}
<UserSearchMainView_V2 />
</>
);
}

View File

@@ -1,59 +1,6 @@
/* eslint-disable react-hooks/exhaustive-deps */
import {
LoaderCustom,
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 { useFocusEffect } from "expo-router";
import _ from "lodash";
import { useState, useCallback } from "react";
import Voting_ScreenContribution from "@/screens/Voting/ScreenContribution";
export default function VotingContribution() {
const { user } = useAuth();
const [listData, setListData] = useState<any>([]);
const [loadingGetData, setLoadingGetData] = useState(false);
useFocusEffect(
useCallback(() => {
onLoadData();
}, [])
);
const onLoadData = async () => {
try {
setLoadingGetData(true);
const response = await apiVotingGetAll({
category: "contribution",
authorId: user?.id as string,
});
if (response.success) {
setListData(response.data);
}
} catch (error) {
console.log("[ERROR]", error);
} finally {
setLoadingGetData(false);
}
};
return (
<ViewWrapper hideFooter>
{loadingGetData ? (
<LoaderCustom />
) : _.isEmpty(listData) ? (
<TextCustom align="center">Tidak ada kontribusi</TextCustom>
) : listData.map((item: any, index: number) => (
<Voting_BoxPublishSection
data={item}
key={index}
href={`/voting/${item.id}/contribution`}
/>
))}
</ViewWrapper>
);
return <Voting_ScreenContribution />;
}

View File

@@ -1,77 +1,10 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { LoaderCustom, TextCustom, ViewWrapper } from "@/components";
import TabsTwoButtonCustom from "@/components/_ShareComponent/TabsTwoHeaderCustom";
import Voting_BoxPublishSection from "@/screens/Voting/BoxPublishSection";
import { useAuth } from "@/hooks/use-auth";
import { useCallback, useState } from "react";
import { apiVotingGetAll } from "@/service/api-client/api-voting";
import { useFocusEffect } from "expo-router";
import _ from "lodash";
import Voting_ScreenHistory from "@/screens/Voting/ScreenHistory";
export default function VotingHistory() {
const { user } = useAuth();
const [activeCategory, setActiveCategory] = useState<string | null>("all");
const [listData, setListData] = useState<any>([]);
const [loadingGetData, setLoadingGetData] = useState(false);
useFocusEffect(
useCallback(() => {
onLoadData();
}, [activeCategory])
);
const onLoadData = async () => {
try {
setLoadingGetData(true);
const response = await apiVotingGetAll({
category: activeCategory === "all" ? "all-history" : "my-history",
authorId: user?.id as string,
});
if (response.success) {
setListData(response.data);
}
} catch (error) {
console.log("[ERROR]", error);
} finally {
setLoadingGetData(false);
}
};
const handlePress = (item: any) => {
setActiveCategory(item);
// tambahkan logika lain seperti filter dsb.
};
return (
<ViewWrapper
hideFooter
headerComponent={
<TabsTwoButtonCustom
leftValue="all"
rightValue="main"
leftText="Semua Riwayat"
rightText="Riwayat Saya"
activeCategory={activeCategory}
handlePress={handlePress}
/>
}
>
{loadingGetData ? (
<LoaderCustom />
) : _.isEmpty(listData) ? (
<TextCustom align="center">Tidak ada riwayat</TextCustom>
) : (
listData.map((item: any, index: number) => (
<Voting_BoxPublishSection
key={index}
id={item.id}
data={item}
href={`/voting/${item.id}/history`}
/>
))
)}
</ViewWrapper>
<>
<Voting_ScreenHistory />
</>
);
}

View File

@@ -1,71 +1,10 @@
/* eslint-disable react-hooks/exhaustive-deps */
import {
FloatingButton,
LoaderCustom,
SearchInput,
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";
import _ from "lodash";
import { useCallback, useState } from "react";
import Voting_ScreenBeranda from "@/screens/Voting/ScreenBeranda";
export default function VotingBeranda() {
const { user } = useAuth();
const [listData, setListData] = useState<any>([]);
const [loadingGetData, setLoadingGetData] = useState(false);
const [search, setSearch] = useState("");
useFocusEffect(
useCallback(() => {
onLoadData();
}, [search])
);
const onLoadData = async () => {
try {
setLoadingGetData(true);
const response = await apiVotingGetAll({
search,
category: "beranda",
userLoginId: user?.id,
});
if (response.success) {
setListData(response.data);
}
} catch (error) {
console.log("[ERROR]", error);
} finally {
setLoadingGetData(false);
}
};
return (
<ViewWrapper
hideFooter
floatingButton={
<FloatingButton onPress={() => router.push("/voting/create")} />
}
headerComponent={
<SearchInput placeholder="Cari voting" onChangeText={setSearch} />
}
>
{loadingGetData ? (
<LoaderCustom />
) : _.isEmpty(listData) ? (
<TextCustom align="center">Tidak ada data</TextCustom>
) : (
listData.map((item: any, index: number) => (
<Voting_BoxPublishSection
data={item}
key={index}
href={`/voting/${item.id}`}
/>
))
)}
</ViewWrapper>
<>
<Voting_ScreenBeranda />
</>
);
}

View File

@@ -1,106 +1,10 @@
/* eslint-disable react-hooks/exhaustive-deps */
import {
BadgeCustom,
BaseBox,
LoaderCustom,
ScrollableCustom,
StackCustom,
TextCustom,
ViewWrapper,
} from "@/components";
import { useAuth } from "@/hooks/use-auth";
import { dummyMasterStatus } from "@/lib/dummy-data/_master/status";
import { apiVotingGetByStatus } from "@/service/api-client/api-voting";
import { dateTimeView } from "@/utils/dateTimeView";
import { useFocusEffect, useLocalSearchParams } from "expo-router";
import _ from "lodash";
import { useCallback, useState } from "react";
import Voting_ScreenStatus from "@/screens/Voting/ScreenStatus";
export default function VotingStatus() {
const { user } = useAuth();
const { status } = useLocalSearchParams<{ status?: string }>();
const id = user?.id || "";
const [activeCategory, setActiveCategory] = useState<string | null>(
status || "publish"
);
const [listData, setListData] = useState([]);
const [loadingGetData, setLoadingGetData] = useState(false);
useFocusEffect(
useCallback(() => {
onLoadData();
}, [activeCategory, id])
);
async function onLoadData() {
try {
setLoadingGetData(true);
const response = await apiVotingGetByStatus({
id: id as string,
status: activeCategory!,
});
setListData(response.data);
} catch (error) {
console.log(error);
} finally {
setLoadingGetData(false);
}
}
const handlePress = (item: any) => {
setActiveCategory(item.value);
// tambahkan logika lain seperti filter dsb.
};
const scrollComponent = (
<ScrollableCustom
data={dummyMasterStatus.map((e, i) => ({
id: i,
label: e.label,
value: e.value,
}))}
onButtonPress={handlePress}
activeId={activeCategory as any}
/>
);
return (
<ViewWrapper headerComponent={scrollComponent} hideFooter>
{loadingGetData ? (
<LoaderCustom />
) : _.isEmpty(listData) ? (
<TextCustom align="center">Tidak ada data {activeCategory}</TextCustom>
) : (
listData.map((item: any, i: number) => (
<BaseBox
key={i}
paddingTop={20}
paddingBottom={20}
href={`/voting/${item.id}/${activeCategory}/detail`}
>
<StackCustom>
<TextCustom align="center" bold truncate={2} size="large">
{item?.title || ""}
</TextCustom>
<BadgeCustom
style={{ width: "70%", alignSelf: "center" }}
variant="light"
>
{item?.awalVote &&
dateTimeView({
date: item?.awalVote,
withoutTime: true,
})}{" "}
-{" "}
{item?.akhirVote &&
dateTimeView({ date: item?.akhirVote, withoutTime: true })}
</BadgeCustom>
</StackCustom>
</BaseBox>
))
)}
</ViewWrapper>
<>
<Voting_ScreenStatus />
</>
);
}

View File

@@ -51,8 +51,6 @@ 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);
}

View File

@@ -5,13 +5,14 @@ import {
ButtonCustom,
CenterCustom,
LoaderCustom,
NewWrapper,
Spacing,
StackCustom,
TextAreaCustom,
TextCustom,
TextInputCustom,
ViewWrapper,
TextInputCustom
} from "@/components";
import ListSkeletonComponent from "@/components/_ShareComponent/ListSkeletonComponent";
import DateTimePickerCustom from "@/components/DateInput/DateTimePickerCustom";
import { MainColor } from "@/constants/color-palet";
import { ICON_SIZE_XLARGE } from "@/constants/constans-value";
@@ -34,7 +35,7 @@ interface IEditData {
Voting_DaftarNamaVote?: [
{
value?: string;
}
},
];
}
@@ -47,7 +48,7 @@ export default function VotingEdit() {
useFocusEffect(
useCallback(() => {
onLoadData();
}, [id])
}, [id]),
);
const onLoadData = async () => {
@@ -188,9 +189,9 @@ export default function VotingEdit() {
};
return (
<ViewWrapper footerComponent={buttonSubmit()}>
<NewWrapper footerComponent={buttonSubmit()}>
{loadingGetData ? (
<LoaderCustom />
<ListSkeletonComponent />
) : (
<StackCustom gap={"xs"}>
<TextInputCustom
@@ -210,7 +211,7 @@ export default function VotingEdit() {
onChangeText={(text) => setData({ ...data, deskripsi: text })}
/>
<Spacing />
<DateTimePickerCustom
minimumDate={new Date(Date.now())}
@@ -255,7 +256,7 @@ export default function VotingEdit() {
}
</TextCustom>
)}
<Spacing />
</StackCustom>
{data?.Voting_DaftarNamaVote?.map((item: any, index: number) => (
@@ -270,7 +271,7 @@ export default function VotingEdit() {
...(data as any),
Voting_DaftarNamaVote: data?.Voting_DaftarNamaVote?.map(
(item: any, i: any) =>
i === index ? { ...item, value } : item
i === index ? { ...item, value } : item,
),
})
}
@@ -327,6 +328,6 @@ export default function VotingEdit() {
<Spacing />
</StackCustom>
)}
</ViewWrapper>
</NewWrapper>
);
}

View File

@@ -1,69 +1,6 @@
/* eslint-disable react-hooks/exhaustive-deps */
import {
AvatarUsernameAndOtherComponent,
BadgeCustom,
BaseBox,
LoaderCustom,
TextCustom,
ViewWrapper,
} from "@/components";
import { apiVotingContribution } from "@/service/api-client/api-voting";
import { useFocusEffect, useLocalSearchParams } from "expo-router";
import _ from "lodash";
import { useCallback, useState } from "react";
import Voting_ScreenListOfContributor from "@/screens/Voting/ScreenListOfContributor";
export default function Voting_ListOfContributor() {
const { id } = useLocalSearchParams();
const [listData, setListData] = useState<any>([]);
const [isLoadData, setIsLoadData] = useState(false);
useFocusEffect(
useCallback(() => {
onLoadList();
}, [id])
);
const onLoadList = async () => {
try {
setIsLoadData(true);
const response = await apiVotingContribution({
id: id as string,
authorId: "",
category: "list",
});
if (response.success) {
setListData(response.data);
}
} catch (error) {
console.log("[ERROR]", error);
} finally {
setIsLoadData(false);
}
};
return (
<ViewWrapper>
{isLoadData ? (
<LoaderCustom />
) : _.isEmpty(listData) ? (
<TextCustom align="center">Tidak ada kontributor</TextCustom>
) : (
listData.map((item: any, index: number) => (
<BaseBox paddingTop={5} paddingBottom={5} key={index.toString()}>
<AvatarUsernameAndOtherComponent
avatar={item?.Author?.Profile?.imageId || ""}
name={item?.Author?.username || "Username"}
avatarHref={`/profile/${item?.Author?.Profile?.id}`}
rightComponent={
<BadgeCustom style={{ alignSelf: "flex-end" }}>
{item?.Voting_DaftarNamaVote?.value}
</BadgeCustom>
}
/>
</BaseBox>
))
)}
</ViewWrapper>
);
export default function VotingListOfContributor() {
return <Voting_ScreenListOfContributor />;
}

View File

@@ -3,11 +3,12 @@ import {
BoxButtonOnFooter,
ButtonCustom,
CenterCustom,
NewWrapper,
Spacing,
StackCustom,
TextAreaCustom,
TextInputCustom,
ViewWrapper
ViewWrapper,
} from "@/components";
import DateTimePickerCustom from "@/components/DateInput/DateTimePickerCustom";
import { MainColor } from "@/constants/color-palet";
@@ -79,7 +80,7 @@ export default function VotingCreate() {
type: "success",
text1: "Data berhasil disimpan",
});
router.replace("/(application)/(user)/voting/(tabs)/status?status=review");
router.replace("/voting/(tabs)/status?status=review");
} else {
Toast.show({
type: "error",
@@ -106,7 +107,7 @@ export default function VotingCreate() {
};
return (
<ViewWrapper footerComponent={buttonSubmit()}>
<NewWrapper footerComponent={buttonSubmit()}>
<StackCustom gap={"xs"}>
<TextInputCustom
label="Judul Voting"
@@ -142,7 +143,6 @@ export default function VotingCreate() {
}
/>
{listVote.map((item, index) => (
<TextInputCustom
key={index}
@@ -153,8 +153,8 @@ export default function VotingCreate() {
onChangeText={(value: any) =>
setListVote(
listVote.map((item, i) =>
i === index ? { ...item, value } : item
)
i === index ? { ...item, value } : item,
),
)
}
/>
@@ -198,6 +198,6 @@ export default function VotingCreate() {
<Spacing />
</StackCustom>
</ViewWrapper>
</NewWrapper>
);
}

View File

@@ -8,6 +8,8 @@ import {
} from "@/components";
import DrawerAdmin from "@/components/Drawer/DrawerAdmin";
import NavbarMenu from "@/components/Drawer/NavbarMenu";
import NavbarMenu_V2 from "@/components/Drawer/NavbarMenu_V2";
import NavbarMenu_V3 from "@/components/Drawer/NavbarMenu_V3";
import { AccentColor, MainColor } from "@/constants/color-palet";
import {
ICON_SIZE_MEDIUM,
@@ -15,11 +17,16 @@ import {
ICON_SIZE_XLARGE,
} from "@/constants/constans-value";
import { useAuth } from "@/hooks/use-auth";
import { useNotificationStore } from "@/hooks/use-notification-store";
import AdminNotificationBell from "@/screens/Admin/AdminNotificationBell";
import {
adminListMenu,
superAdminListMenu,
} from "@/screens/Admin/listPageAdmin";
import {
adminListMenu_V2,
superAdminListMenu_V2,
} from "@/screens/Admin/listPageAdmin_V2";
import { GStyles } from "@/styles/global-styles";
import { FontAwesome6, Ionicons } from "@expo/vector-icons";
import { router, Stack } from "expo-router";
@@ -148,6 +155,24 @@ export default function AdminLayout() {
}
onClose={() => setOpenDrawerNavbar(false)}
/>
{/* <NavbarMenu_V2
items={
user?.masterUserRoleId === "2"
? adminListMenu_V2
: superAdminListMenu_V2
}
onClose={() => setOpenDrawerNavbar(false)}
/> */}
{/* <NavbarMenu_V3
items={
user?.masterUserRoleId === "2"
? adminListMenu_V2
: superAdminListMenu_V2
}
onClose={() => setOpenDrawerNavbar(false)}
/> */}
</StackCustom>
</DrawerAdmin>
@@ -198,7 +223,7 @@ export default function AdminLayout() {
// size={ICON_SIZE_SMALL}
// color={MainColor.white}
// />
<AdminNotificationBell/>
<AdminNotificationBell />
),
path: "/admin/notification",
},

View File

@@ -1,135 +1,5 @@
/* eslint-disable react-hooks/exhaustive-deps */
import {
ActionIcon,
BaseBox,
CenterCustom,
LoaderCustom,
Spacing,
StackCustom,
TextCustom,
ViewWrapper,
} from "@/components";
import { IconEdit } from "@/components/_Icon";
import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButtonAntTitle";
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";
import { Admin_ScreenBusinessFieldDetail } from "@/screens/Admin/App-Information/ScreenBusinessFieldDetail";
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: "all",
});
console.log("Response >>", JSON.stringify(response, null, 2));
setData(response.data);
} catch (error) {
console.log("[ERROR]", error);
setData(null);
}
};
return (
<>
<ViewWrapper>
<StackCustom>
<AdminBackButtonAntTitle title="Detail Bidang & Sub Bidang" />
{!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>
</>
);
return <Admin_ScreenBusinessFieldDetail />;
}

View File

@@ -1,86 +1,5 @@
import { ScrollableCustom, StackCustom, ViewWrapper } from "@/components";
import AdminActionIconPlus from "@/components/_ShareComponent/Admin/ActionIconPlus";
import AdminComp_BoxTitle from "@/components/_ShareComponent/Admin/BoxTitlePage";
import AdminAppInformation_BusinessFieldSection from "@/screens/Admin/App-Information/BusinessFieldSection";
import AdminAppInformation_Bank from "@/screens/Admin/App-Information/InformationBankSection";
import AdminAppInformation_StickerSection from "@/screens/Admin/App-Information/StickerSection";
import { router } from "expo-router";
import { useState } from "react";
import { Alert } from "react-native";
import { Admin_ScreenAppInformation } from "@/screens/Admin/App-Information/ScreenAppInformation";
export default function AdminInformation() {
const [activeCategory, setActiveCategory] = useState<string | null>("bank");
const [activePage, setActivePage] = useState<string>("Informasi Bank");
const handlePress = (item: any) => {
setActiveCategory(item.value);
setActivePage(item.label);
// tambahkan logika lain seperti filter dsb.
};
const scrollComponent = (
<StackCustom>
<ScrollableCustom
data={listPage}
onButtonPress={handlePress}
activeId={activeCategory as any}
/>
</StackCustom>
);
const renderContent = () => {
switch (activeCategory) {
case "bank":
return <AdminAppInformation_Bank />;
case "business":
return <AdminAppInformation_BusinessFieldSection />;
case "sticker":
return <AdminAppInformation_StickerSection />;
default:
return <AdminAppInformation_Bank />;
}
};
return (
<>
<ViewWrapper headerComponent={scrollComponent}>
<AdminComp_BoxTitle
title={activePage}
rightComponent={
<AdminActionIconPlus
onPress={() => {
if (activeCategory === "bank") {
router.push("/admin/app-information/information-bank/create");
} else if (activeCategory === "business") {
router.push("/admin/app-information/business-field/create");
} else if (activeCategory === "sticker") {
Alert.alert("Coming Soon", "Next Update");
// router.push("/admin/app-information/sticker/create");
}
}}
/>
}
/>
{renderContent()}
</ViewWrapper>
</>
);
return <Admin_ScreenAppInformation />;
}
const listPage = [
{
id: "1",
label: "Informasi Bank",
value: "bank",
},
{
id: "2",
label: "Bidang & Sub Bidang",
value: "business",
},
{
id: "3",
label: "Stiker",
value: "sticker",
},
];

View File

@@ -1,12 +1,12 @@
/* eslint-disable react-hooks/exhaustive-deps */
import {
BaseBox,
BoxButtonOnFooter,
ButtonCustom,
Grid,
StackCustom,
TextCustom,
ViewWrapper,
BaseBox,
BoxButtonOnFooter,
ButtonCustom,
Grid,
StackCustom,
TextCustom,
ViewWrapper,
} from "@/components";
import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButtonAntTitle";
import GridTwoView from "@/components/_ShareComponent/GridTwoView";
@@ -22,7 +22,7 @@ export default function AdminCollaborationPublish() {
useFocusEffect(
useCallback(() => {
handlerLoadData();
}, [status])
}, [status]),
);
const handlerLoadData = async () => {
@@ -78,16 +78,16 @@ export default function AdminCollaborationPublish() {
</StackCustom>
</BaseBox>
{data?.report && (
<BaseBox>
<GridTwoView
spanLeft={4}
spanRight={8}
leftIcon={<TextCustom bold>Catatan report</TextCustom>}
rightIcon={<TextCustom>{data?.report}</TextCustom>}
/>
</BaseBox>
)}
{data?.report && (
<BaseBox>
<GridTwoView
spanLeft={4}
spanRight={8}
leftItem={<TextCustom bold>Catatan report</TextCustom>}
rightItem={<TextCustom>{data?.report}</TextCustom>}
/>
</BaseBox>
)}
</ViewWrapper>
</>
);

View File

@@ -1,4 +1,5 @@
import {
AlertDefaultSystem,
BadgeCustom,
BaseBox,
BoxButtonOnFooter,
@@ -9,6 +10,7 @@ import {
} from "@/components";
import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButtonAntTitle";
import { GridSpan_4_8 } from "@/components/_ShareComponent/GridSpan_4_8";
import { useAuth } from "@/hooks/use-auth";
import {
apiAdminDonationInvoiceDetailById,
apiAdminDonationInvoiceUpdateById,
@@ -22,6 +24,7 @@ import { useCallback, useState } from "react";
import Toast from "react-native-toast-message";
export default function AdminDonasiTransactionDetail() {
const { user } = useAuth();
const { id, status } = useLocalSearchParams();
console.log("[STATUS]", id, status);
@@ -33,7 +36,7 @@ export default function AdminDonasiTransactionDetail() {
onLoadData();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [id])
}, [id]),
);
const onLoadData = async () => {
@@ -57,6 +60,7 @@ export default function AdminDonasiTransactionDetail() {
const newData = {
donationId: data?.donasiId,
nominal: data?.nominal,
senderId: user?.id,
};
const response = await apiAdminDonationInvoiceUpdateById({
@@ -97,7 +101,15 @@ export default function AdminDonasiTransactionDetail() {
<ButtonCustom
isLoading={isLoading}
onPress={() => {
handlerSubmit();
AlertDefaultSystem({
title: "Konfirmasi transaksi",
message: "Apakah anda yakin ingin menyetujui transaksi ini?",
textLeft: "Tidak",
textRight: "Ya",
onPressRight: () => {
handlerSubmit();
},
});
}}
>
Terima donasi
@@ -109,7 +121,7 @@ export default function AdminDonasiTransactionDetail() {
return (
<BoxButtonOnFooter>
<ButtonCustom disabled>
{data?.DonasiMaster_StatusInvoice?.name}
{data?.DonasiMaster_StatusInvoice?.name}
</ButtonCustom>
</BoxButtonOnFooter>
);
@@ -140,7 +152,7 @@ export default function AdminDonasiTransactionDetail() {
})}
>
{_.startCase(
(data?.DonasiMaster_StatusInvoice?.name as any) || "-"
(data?.DonasiMaster_StatusInvoice?.name as any) || "-",
)}
</BadgeCustom>
)) ||
@@ -157,7 +169,7 @@ export default function AdminDonasiTransactionDetail() {
<ButtonCustom
onPress={() =>
router.push(
`/(application)/(image)/preview-image/${data?.imageId}`
`/(application)/(image)/preview-image/${data?.imageId}`,
)
}
>

View File

@@ -42,19 +42,19 @@ export default function AdminDonationDetailDisbursementOfFunds() {
const listData = [
{
label: "Nominal",
value: `Rp ${(data && formatCurrencyDisplay(data?.nominalCair)) || 0}`,
value: `Rp ${data ? formatCurrencyDisplay(data?.nominalCair) : 0}`,
},
{
label: "Tanggal",
value: dateTimeView({ date: data?.createdAt }),
value: data ? dateTimeView({ date: data?.createdAt }) : "-",
},
{
label: "Judul",
value: (data && data?.title) || "-",
value: data ? data?.title : "-",
},
{
label: "Deskripsi",
value: (data && data?.deskripsi) || "-",
value: data ? data?.deskripsi : "-",
},
];
return (

View File

@@ -14,7 +14,11 @@ import {
} from "@/components";
import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButtonAntTitle";
import DIRECTORY_ID from "@/constants/directory-id";
import { apiAdminDonationDetailById, apiAdminDonationDisbursementOfFundsCreated } from "@/service/api-admin/api-admin-donation";
import { useAuth } from "@/hooks/use-auth";
import {
apiAdminDonationDetailById,
apiAdminDonationDisbursementOfFundsCreated,
} from "@/service/api-admin/api-admin-donation";
import { uploadFileService } from "@/service/upload-service";
import { formatCurrencyDisplay } from "@/utils/formatCurrencyDisplay";
import pickFile from "@/utils/pickFile";
@@ -25,7 +29,7 @@ import Toast from "react-native-toast-message";
export default function AdminDonationDisbursementOfFunds() {
const { id } = useLocalSearchParams();
const { user } = useAuth();
const [data, setData] = React.useState<any | null>(null);
const [isLoading, setIsLoading] = React.useState(false);
@@ -40,7 +44,7 @@ export default function AdminDonationDisbursementOfFunds() {
useFocusEffect(
React.useCallback(() => {
onLoadData();
}, [id])
}, [id]),
);
const onLoadData = async () => {
@@ -94,6 +98,7 @@ export default function AdminDonationDisbursementOfFunds() {
const newData = {
...value,
authorId: user?.id,
imageId: imageId,
};

View File

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

View File

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

View File

@@ -7,15 +7,15 @@ import {
} from "@/components";
import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButtonAntTitle";
import AdminButtonReject from "@/components/_ShareComponent/Admin/ButtonReject";
import { useAuth } from "@/hooks/use-auth";
import { funUpdateStatusDonation } from "@/screens/Admin/Donation/funDonationUpdateStatus";
import {
apiAdminDonationDetailById
} from "@/service/api-admin/api-admin-donation";
import { apiAdminDonationDetailById } from "@/service/api-admin/api-admin-donation";
import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
import React from "react";
import Toast from "react-native-toast-message";
export default function AdminDonationRejectInput() {
const { user } = useAuth();
const { id, status } = useLocalSearchParams();
const [data, setData] = React.useState<any | null>(null);
@@ -24,7 +24,7 @@ export default function AdminDonationRejectInput() {
useFocusEffect(
React.useCallback(() => {
onLoadData();
}, [id])
}, [id]),
);
const onLoadData = async () => {
@@ -48,11 +48,23 @@ export default function AdminDonationRejectInput() {
changeStatus: "publish" | "review" | "reject";
}) => {
try {
if (!user?.id) {
Toast.show({
type: "error",
text1: "User tidak ditemukan",
});
return;
}
setIsLoading(true);
const response = await funUpdateStatusDonation({
id: id as string,
changeStatus,
data: data,
data: {
senderId: user?.id as string,
catatan: data,
},
});
if (!response.success) {
@@ -61,7 +73,7 @@ export default function AdminDonationRejectInput() {
text1: "Report gagal",
});
return
return;
}
Toast.show({

View File

@@ -1,117 +1,5 @@
/* eslint-disable react-hooks/exhaustive-deps */
import {
ActionIcon,
LoaderCustom,
SearchInput,
StackCustom,
TextCustom,
ViewWrapper
} from "@/components";
import AdminComp_BoxTitle from "@/components/_ShareComponent/Admin/BoxTitlePage";
import AdminTitleTable from "@/components/_ShareComponent/Admin/TableTitle";
import AdminTableValue from "@/components/_ShareComponent/Admin/TableValue";
import AdminTitlePage from "@/components/_ShareComponent/Admin/TitlePage";
import { ICON_SIZE_BUTTON } from "@/constants/constans-value";
import { apiAdminDonation } from "@/service/api-admin/api-admin-donation";
import { Octicons } from "@expo/vector-icons";
import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
import _ from "lodash";
import { useCallback, useState } from "react";
import { Divider } from "react-native-paper";
import { Admin_ScreenDonationStatus } from "@/screens/Admin/Donation/ScreenDonationStatus";
export default function AdminDonationStatus() {
const { status } = useLocalSearchParams();
console.log("[STATUS]", status);
const [data, setData] = useState<any | null>(null);
const [search, setSearch] = useState<string>("");
const [loadData, setLoadData] = useState<boolean>(false);
useFocusEffect(
useCallback(() => {
onLoadData();
}, [status, search])
);
const onLoadData = async () => {
try {
setLoadData(true);
const response = await apiAdminDonation({
category: status as "publish" | "review" | "reject",
search,
});
console.log("[RES]", JSON.stringify(response, null, 2));
if (response.success) {
setData(response.data);
}
} catch (error) {
console.log("[ERROR]", error);
setData([]);
} finally {
setLoadData(false);
}
};
const rightComponent = (
<SearchInput
containerStyle={{ width: "100%", marginBottom: 0 }}
placeholder="Cari"
value={search}
onChangeText={(value) => setSearch(value)}
/>
);
return (
<>
<ViewWrapper headerComponent={<AdminTitlePage title="Donasi" />}>
<StackCustom gap={"sm"}>
<AdminComp_BoxTitle
title={`${_.startCase(status as string)}`}
rightComponent={rightComponent}
/>
<AdminTitleTable
title1="Aksi"
title2="Username"
title3="Judul Donasi"
/>
<Divider />
{loadData ? (
<LoaderCustom />
) : _.isEmpty(data) ? (
<TextCustom align="center" size="small" color="gray">
Belum ada data
</TextCustom>
) : (
data?.map((item: any, index: number) => (
<AdminTableValue
key={index}
value1={
<ActionIcon
icon={
<Octicons
name="eye"
size={ICON_SIZE_BUTTON}
color="black"
/>
}
onPress={() => {
router.push(`/admin/donation/${item.id}/${status}`);
}}
/>
}
value2={<TextCustom truncate={1}>{item?.Author?.username || "-"}</TextCustom>}
value3={
<TextCustom truncate={2}>
{item?.title || "-"}
</TextCustom>
}
/>
))
)}
</StackCustom>
</ViewWrapper>
</>
);
return <Admin_ScreenDonationStatus />;
}

View File

@@ -30,7 +30,6 @@ export default function AdminDonationCategoryUpdate() {
const response = await apiAdminMasterDonationCategoryById({
id: id as any,
});
console.log(JSON.stringify(response.data, null, 2));
setData(response.data);
};
@@ -44,10 +43,9 @@ export default function AdminDonationCategoryUpdate() {
id: id as any,
data: data,
});
console.log(JSON.stringify(response.data, null, 2));
router.back();
} catch (error) {
console.log(error);
console.log("Error update category:", error);
} finally {
setIsLoading(false);
}

View File

@@ -1,135 +1,5 @@
import {
BadgeCustom,
CenterCustom,
ClickableCustom,
Spacing,
StackCustom,
TextCustom,
ViewWrapper
} from "@/components";
import AdminActionIconPlus from "@/components/_ShareComponent/Admin/ActionIconPlus";
import AdminComp_BoxTitle from "@/components/_ShareComponent/Admin/BoxTitlePage";
import AdminTitlePage from "@/components/_ShareComponent/Admin/TitlePage";
import { GridSpan_4_8 } from "@/components/_ShareComponent/GridSpan_4_8";
import { apiAdminMasterDonationCategory } from "@/service/api-admin/api-master-admin";
import { colorActivationForBadge } from "@/utils/colorActivationForBadge";
import { router, useFocusEffect } from "expo-router";
import { useCallback, useState } from "react";
import { RefreshControl, View } from "react-native";
import { Divider } from "react-native-paper";
import { Admin_ScreenDonationCategory } from "@/screens/Admin/Donation/ScreenDonationCategory";
export default function AdminDonationCategory() {
const [listData, setListData] = useState<any[]>([]);
const [refreshing, setRefreshing] = useState(false);
const [loading, setLoading] = useState(false);
useFocusEffect(
useCallback(() => {
fetchMaster();
}, [])
);
const fetchMaster = async () => {
try {
setLoading(true);
const response = await apiAdminMasterDonationCategory();
if (response.success) {
console.log(JSON.stringify(response.data, null, 2));
setListData(response.data);
} else {
setListData([]);
}
} catch (error) {
console.log("[Error]", error);
} finally {
setLoading(false);
}
};
const onRefresh = async () => {
setRefreshing(true);
await fetchMaster();
setRefreshing(false);
};
return (
<>
<ViewWrapper
refreshControl={
<RefreshControl refreshing={refreshing} onRefresh={onRefresh} />
}
headerComponent={<AdminTitlePage title="Donasi" />}
>
<AdminComp_BoxTitle
title="Kategori"
rightComponent={
<AdminActionIconPlus
onPress={() => {
router.push(`/admin/donation/category-create`);
}}
/>
}
/>
<View>
<GridSpan_4_8
label={<TextCustom bold>Status</TextCustom>}
value={<TextCustom bold>Kategori</TextCustom>}
/>
{/* <Grid>
<Grid.Col style={{ paddingLeft: 10 }} span={4}>
<TextCustom bold>Status</TextCustom>
</Grid.Col>
<Grid.Col span={8}>
<TextCustom bold>Kategori</TextCustom>
</Grid.Col>
</Grid> */}
<Divider />
<Spacing />
<StackCustom>
{listData.map((item, index) => (
<ClickableCustom
onPress={() => {
router.push(`/admin/donation/category-update?id=${item.id}`);
}}
key={index}
>
<GridSpan_4_8
label={
<CenterCustom>
<BadgeCustom
color={colorActivationForBadge({
status: item.active,
})}
>
{item.active ? "Aktif" : "Tidak Aktif"}
</BadgeCustom>
</CenterCustom>
}
value={<TextCustom>{item.name}</TextCustom>}
/>
{/* <Grid containerStyle={{ paddingBottom: 10 }}>
<Grid.Col span={4} style={{ paddingLeft: 10 }}>
<CenterCustom>
<BadgeCustom
color={item.active ? MainColor.green : MainColor.red}
>
{item.active ? "Aktif" : "Tidak Aktif"}
</BadgeCustom>
</CenterCustom>
</Grid.Col>
<Grid.Col span={8}>
<TextCustom bold>{item.name}</TextCustom>
</Grid.Col>
</Grid> */}
<Divider />
</ClickableCustom>
))}
</StackCustom>
</View>
</ViewWrapper>
</>
);
return <Admin_ScreenDonationCategory />;
}

View File

@@ -1,254 +1,5 @@
/* eslint-disable react-hooks/exhaustive-deps */
import {
ActionIcon,
AlertDefaultSystem,
BadgeCustom,
BaseBox,
DrawerCustom,
LoaderCustom,
MenuDrawerDynamicGrid,
Spacing,
StackCustom,
TextCustom,
ViewWrapper,
} from "@/components";
import { IconDot, IconList } from "@/components/_Icon/IconComponent";
import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButtonAntTitle";
import AdminButtonReject from "@/components/_ShareComponent/Admin/ButtonReject";
import AdminButtonReview from "@/components/_ShareComponent/Admin/ButtonReview";
import { GridSpan_4_8 } from "@/components/_ShareComponent/GridSpan_4_8";
import ReportBox from "@/components/Box/ReportBox";
import { ICON_SIZE_BUTTON } from "@/constants/constans-value";
import { useAuth } from "@/hooks/use-auth";
import { funUpdateStatusEvent } from "@/screens/Admin/Event/funUpdateStatus";
import { apiAdminEventById } from "@/service/api-admin/api-admin-event";
import { DEEP_LINK_URL } from "@/service/api-config";
import { colorBadgeStatus } from "@/utils/colorBadge";
import { dateTimeView } from "@/utils/dateTimeView";
import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
import _ from "lodash";
import React, { useCallback } from "react";
import QRCode from "react-native-qrcode-svg";
import Toast from "react-native-toast-message";
import { Admin_ScreenEventDetail } from "@/screens/Admin/Event/ScreenEventDetail";
export default function AdminEventDetail() {
const { user } = useAuth();
const { id, status } = useLocalSearchParams();
const [openDrawer, setOpenDrawer] = React.useState(false);
const [data, setData] = React.useState<any | null>(null);
const [loadData, setLoadData] = React.useState(false);
const deepLinkURL = `${DEEP_LINK_URL}/event/${id}/confirmation?userId=${user?.id}`;
const deepLinkURLDEV = `${DEEP_LINK_URL}/--/event/${id}/confirmation?userId=${user?.id}`;
const isDevLink =
process.env.NODE_ENV === "development" ? deepLinkURLDEV : deepLinkURL;
useFocusEffect(
useCallback(() => {
onLoadData();
}, [id])
);
const onLoadData = async () => {
try {
setLoadData(true);
const response = await apiAdminEventById({
id: id as string,
});
if (response.success) {
setData(response.data);
}
} catch (error) {
console.log("[ERROR]", error);
} finally {
setLoadData(false);
}
};
const listData = [
{
label: "Pembuat Event",
value: (data && data?.Author?.username) || "-",
},
{
label: "Judul Event",
value: (data && data?.title) || "-",
},
{
label: "Status",
value:
(data && (
<BadgeCustom color={colorBadgeStatus({ status: status as string })}>
{_.startCase(status as string)}
</BadgeCustom>
)) ||
"-",
},
{
label: "Lokasi",
value: (data && data?.lokasi) || "-",
},
{
label: "Tipe Acara",
value: (data && data?.EventMaster_TipeAcara?.name) || "-",
},
{
label: "Mulai Event",
value:
(data && data?.tanggal && dateTimeView({ date: data?.tanggal })) || "-",
},
{
label: "Event Berakhir",
value:
(data &&
data?.tanggalSelesai &&
dateTimeView({ date: data?.tanggalSelesai })) ||
"-",
},
{
label: "Deskripsi",
value: (data && data?.deskripsi) || "-",
},
];
const rightComponent = (
<ActionIcon
icon={<IconDot size={ICON_SIZE_BUTTON} />}
onPress={() => {
setOpenDrawer(true);
}}
/>
);
const handlerSubmit = async () => {
try {
const response = await funUpdateStatusEvent({
id: id as string,
changeStatus: "publish",
data: { catatan: "", senderId: user?.id as string },
});
if (!response.success) {
Toast.show({
type: "error",
text1: "Gagal mempublikasikan event",
});
return;
}
Toast.show({
type: "success",
text1: "Event berhasil dipublikasikan",
});
router.back();
} catch (error) {
console.log("[ERROR]", error);
}
};
return (
<>
<ViewWrapper
headerComponent={
<AdminBackButtonAntTitle
title={`Detail Data`}
rightComponent={
(status === "publish" || status === "history") && rightComponent
}
/>
}
>
<BaseBox>
<StackCustom>
{listData.map((item, i) => (
<GridSpan_4_8
key={i}
label={<TextCustom bold>{item.label}</TextCustom>}
value={<TextCustom>{item.value}</TextCustom>}
/>
))}
</StackCustom>
<Spacing />
</BaseBox>
{data &&
data?.catatan &&
(status === "reject" || status === "review") && (
<ReportBox text={data?.catatan} />
)}
{(status === "publish" || status === "history") && (
<BaseBox>
<StackCustom style={{ alignItems: "center" }}>
<TextCustom bold>QR Code Event</TextCustom>
{loadData ? (
<LoaderCustom />
) : (
<QRCode
value={isDevLink}
size={200}
// logo={require("@/assets/images/logo-hipmi.png")}
// logoSize={70}
// logoBackgroundColor="transparent"
// logoBorderRadius={50}
// color="black"
/>
)}
<TextCustom align="center">{isDevLink}</TextCustom>
</StackCustom>
</BaseBox>
)}
{status === "review" && (
<AdminButtonReview
onPublish={() => {
AlertDefaultSystem({
title: "Publish",
message: "Apakah anda yakin ingin mempublikasikan data ini?",
textLeft: "Batal",
textRight: "Ya",
onPressRight: () => handlerSubmit(),
});
}}
onReject={() => {
router.push(`/admin/event/${id}/reject-input?status=${status}`);
}}
/>
)}
{status === "reject" && (
<AdminButtonReject
title="Tambah Catatan"
onReject={() => {
router.push(`/admin/event/${id}/reject-input?status=${status}`);
}}
/>
)}
<Spacing />
</ViewWrapper>
<DrawerCustom
isVisible={openDrawer}
closeDrawer={() => setOpenDrawer(false)}
height={"auto"}
>
<MenuDrawerDynamicGrid
data={[
{
label: "Daftar Peserta",
icon: <IconList />,
path: `/admin/event/${id}/list-of-participants`,
},
]}
onPressItem={(item) => {
setOpenDrawer(false);
router.push(item.path as any);
}}
/>
</DrawerCustom>
</>
);
return <Admin_ScreenEventDetail />;
}

View File

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

View File

@@ -1,135 +1,5 @@
/* eslint-disable react-hooks/exhaustive-deps */
import {
ActionIcon,
ClickableCustom,
LoaderCustom,
SearchInput,
StackCustom,
TextCustom,
ViewWrapper,
} from "@/components";
import AdminComp_BoxTitle from "@/components/_ShareComponent/Admin/BoxTitlePage";
import AdminTitleTable from "@/components/_ShareComponent/Admin/TableTitle";
import AdminTableValue from "@/components/_ShareComponent/Admin/TableValue";
import AdminTitlePage from "@/components/_ShareComponent/Admin/TitlePage";
import { ICON_SIZE_BUTTON } from "@/constants/constans-value";
import { apiAdminEvent } from "@/service/api-admin/api-admin-event";
import { dateTimeView } from "@/utils/dateTimeView";
import { Octicons } from "@expo/vector-icons";
import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
import _ from "lodash";
import { useCallback, useState } from "react";
import { Divider } from "react-native-paper";
import { Admin_ScreenEventStatus } from "@/screens/Admin/Event/ScreenEventStatus";
export default function AdminEventStatus() {
const { status } = useLocalSearchParams();
console.log("[STATUS EVENT]", status);
const [listData, setListData] = useState<any[] | null>(null);
const [loadData, setLoadData] = useState(false);
const [search, setSearch] = useState<string>("");
useFocusEffect(
useCallback(() => {
onLoadData();
}, [status, search])
);
const onLoadData = async () => {
try {
setLoadData(true);
const response = await apiAdminEvent({
category: status as "publish" | "review" | "reject" | "history" as any,
search,
});
console.log(
`[RES LIST BY STATUS: ${status}]`,
JSON.stringify(response, null, 2)
);
if (response.success) {
setListData(response.data);
}
} catch (error) {
console.log("[ERROR]", error);
} finally {
setLoadData(false);
}
};
const rightComponent = (
<SearchInput
containerStyle={{ width: "100%", marginBottom: 0 }}
placeholder="Cari"
value={search}
onChangeText={(value) => setSearch(value)}
/>
);
return (
<>
<ViewWrapper headerComponent={<AdminTitlePage title="Event" />}>
<AdminComp_BoxTitle
title={`${_.startCase(status as string)}`}
rightComponent={rightComponent}
/>
<StackCustom gap={"sm"}>
<AdminTitleTable
title1="Username"
title2="Tanggal"
title3="Judul Event"
/>
<Divider />
{loadData ? (
<LoaderCustom />
) : _.isEmpty(listData) ? (
<TextCustom align="center" size="small" color="gray">
Belum ada data
</TextCustom>
) : (
listData?.map((item, index) => (
<ClickableCustom
key={index}
onPress={() => {
router.push(`/admin/event/${item.id}/${status}`);
}}
>
<AdminTableValue
key={index}
value1={
<TextCustom truncate={1}>
{item?.Author?.username || "-"}
</TextCustom>
// <ActionIcon
// icon={
// <Octicons
// name="eye"
// size={ICON_SIZE_BUTTON}
// color="black"
// />
// }
// onPress={() => {
// router.push(`/admin/event/${item.id}/${status}`);
// }}
// />
}
value2={
<TextCustom truncate={1}>
{dateTimeView({ date: item?.tanggal })}
</TextCustom>
}
value3={
<TextCustom truncate={2}>{item?.title || "-"}</TextCustom>
}
/>
<Divider/>
</ClickableCustom>
))
)}
</StackCustom>
</ViewWrapper>
</>
);
return <Admin_ScreenEventStatus />;
}

View File

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

View File

@@ -26,7 +26,7 @@ export default function AdminForumDetailPosting() {
useFocusEffect(
useCallback(() => {
onLoadData();
}, [id])
}, [id]),
);
const onLoadData = async () => {
@@ -72,6 +72,10 @@ export default function AdminForumDetailPosting() {
label: "Total Report",
value: data?.JumlahReportPosting || 0,
},
{
label: "Postingan",
value: (data && data?.diskusi) || "-",
},
];
return (
@@ -111,13 +115,6 @@ export default function AdminForumDetailPosting() {
))}
</StackCustom>
</BaseBox>
<BaseBox>
<StackCustom gap={"sm"}>
<TextCustom bold>Postingan</TextCustom>
<TextCustom>{(data && data?.diskusi) || "-"}</TextCustom>
</StackCustom>
</BaseBox>
</ViewWrapper>
<DrawerCustom

View File

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

View File

@@ -1,262 +1,5 @@
/* eslint-disable react-hooks/exhaustive-deps */
import {
ActionIcon,
AlertDefaultSystem,
BaseBox,
CenterCustom,
DrawerCustom,
LoaderCustom,
MenuDrawerDynamicGrid,
StackCustom,
TextCustom,
ViewWrapper,
} from "@/components";
import { IconDot, IconView } from "@/components/_Icon/IconComponent";
import { IconTrash } from "@/components/_Icon/IconTrash";
import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButtonAntTitle";
import AdminComp_BoxTitle from "@/components/_ShareComponent/Admin/BoxTitlePage";
import { GridSpan_4_8 } from "@/components/_ShareComponent/GridSpan_4_8";
import { GridSpan_NewComponent } from "@/components/_ShareComponent/GridSpan_NewComponent";
import { MainColor } from "@/constants/color-palet";
import { ICON_SIZE_BUTTON } from "@/constants/constans-value";
import { useAuth } from "@/hooks/use-auth";
import {
apiAdminForumCommentById,
apiAdminForumDeactivateComment,
apiAdminForumListReportCommentById,
} from "@/service/api-admin/api-admin-forum";
import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
import _ from "lodash";
import { useCallback, useState } from "react";
import { View } from "react-native";
import { Divider } from "react-native-paper";
import Toast from "react-native-toast-message";
import { Admin_ScreenForumDetailReportComment } from "@/screens/Admin/Forum/ScreenForumDetailReportComment";
export default function AdminForumReportComment() {
const { id } = useLocalSearchParams();
const { user } = useAuth();
const [data, setData] = useState<any | null>(null);
const [listReport, setListReport] = useState<any[] | null>(null);
const [loadList, setLoadList] = useState(false);
const [openDrawer, setOpenDrawer] = useState(false);
const [openDrawerAction, setOpenDrawerAction] = useState(false);
const [selectedReport, setSelectedReport] = useState({
id: "",
username: "",
kategori: "",
keterangan: "",
deskripsi: "",
});
useFocusEffect(
useCallback(() => {
onLoadData();
}, [id])
);
const onLoadData = async () => {
try {
setLoadList(true);
const response = await apiAdminForumCommentById({
id: id as string,
category: "get-one",
});
const responseReport = await apiAdminForumListReportCommentById({
id: id as string,
});
if (response.success) {
setData(response.data);
}
if (responseReport.success) {
setListReport(responseReport.data);
}
} catch (error) {
console.log("[ERROR]", error);
setData(null);
setListReport([]);
} finally {
setLoadList(false);
}
};
return (
<>
<ViewWrapper
headerComponent={
<AdminBackButtonAntTitle
title="Report Komentar"
rightComponent={
<ActionIcon
icon={<IconDot size={16} color={MainColor.darkblue} />}
onPress={() => setOpenDrawer(true)}
/>
}
/>
}
>
<BaseBox>
<StackCustom gap={"sm"}>
<GridSpan_NewComponent
text1={<TextCustom bold>Username</TextCustom>}
text2={<TextCustom>{data?.Author?.username || "-"}</TextCustom>}
/>
<GridSpan_NewComponent
text1={<TextCustom bold>Komentar</TextCustom>}
text2={<TextCustom>{data?.komentar || "-"}</TextCustom>}
/>
</StackCustom>
</BaseBox>
<AdminComp_BoxTitle title="Daftar Report Komentar" />
<StackCustom gap={"sm"}>
<GridSpan_NewComponent
text1={
<TextCustom bold align="center">
Aksi
</TextCustom>
}
text2={<TextCustom bold>Pelapor</TextCustom>}
text3={<TextCustom bold>Kategori Report</TextCustom>}
/>
<Divider />
{loadList ? (
<LoaderCustom />
) : _.isEmpty(listReport) ? (
<TextCustom align="center" color="gray">
Tidak ada report
</TextCustom>
) : (
listReport?.map((item: any, index: number) => (
<View key={index}>
<GridSpan_NewComponent
text1={
<CenterCustom>
<ActionIcon
icon={
<IconView size={ICON_SIZE_BUTTON} color="black" />
}
onPress={() => {
setOpenDrawerAction(true);
setSelectedReport({
id: item.id,
username: item.User?.username,
kategori: item.ForumMaster_KategoriReport?.title,
keterangan:
item.ForumMaster_KategoriReport?.deskripsi,
deskripsi: item.deskripsi,
});
}}
/>
</CenterCustom>
}
text2={
<TextCustom truncate={1}>
{item?.User?.username || "-"}
</TextCustom>
}
text3={
<TextCustom truncate={2}>
{item?.ForumMaster_KategoriReport?.title || "-"}
</TextCustom>
}
/>
<Divider />
</View>
))
)}
</StackCustom>
</ViewWrapper>
<DrawerCustom
isVisible={openDrawer}
closeDrawer={() => setOpenDrawer(false)}
height={"auto"}
>
<MenuDrawerDynamicGrid
data={[
{
icon: <IconTrash />,
label: "Hapus Komentar",
value: "delete",
path: "",
color: MainColor.red,
},
]}
onPressItem={(item) => {
AlertDefaultSystem({
title: "Hapus Komentar",
message: "Apakah Anda yakin ingin menghapus komentar ini?",
textLeft: "Batal",
textRight: "Hapus",
onPressRight: async () => {
const deleteComment = await apiAdminForumDeactivateComment({
id: id as string,
data: {
senderId: user?.id as string,
},
});
// if (!deleteComment.success) {
// Toast.show({
// type: "error",
// text1: "Komentar gagal dihapus",
// });
// return;
// }
setOpenDrawer(false);
Toast.show({
type: "success",
text1: "Komentar berhasil dihapus",
});
router.back();
},
});
}}
/>
</DrawerCustom>
<DrawerCustom
isVisible={openDrawerAction}
closeDrawer={() => setOpenDrawerAction(false)}
height={"auto"}
>
<StackCustom>
<GridSpan_4_8
label={<TextCustom bold>Pelapor</TextCustom>}
value={<TextCustom>{selectedReport?.username || "-"}</TextCustom>}
/>
{selectedReport?.kategori && (
<>
<GridSpan_4_8
label={<TextCustom bold>Kategori Report</TextCustom>}
value={
<TextCustom>{selectedReport?.kategori || "-"}</TextCustom>
}
/>
<GridSpan_4_8
label={<TextCustom bold>Keterangan</TextCustom>}
value={
<TextCustom>{selectedReport?.keterangan || "-"}</TextCustom>
}
/>
</>
)}
{selectedReport?.deskripsi && (
<GridSpan_4_8
label={<TextCustom bold>Deskripsi</TextCustom>}
value={
<TextCustom>{selectedReport?.deskripsi || "-"}</TextCustom>
}
/>
)}
</StackCustom>
</DrawerCustom>
</>
);
return <Admin_ScreenForumDetailReportComment />;
}

View File

@@ -1,283 +1,5 @@
/* eslint-disable react-hooks/exhaustive-deps */
import {
ActionIcon,
AlertDefaultSystem,
BadgeCustom,
BaseBox,
CenterCustom,
DrawerCustom,
LoaderCustom,
MenuDrawerDynamicGrid,
StackCustom,
TextCustom,
ViewWrapper,
} from "@/components";
import { IconDot, IconView } from "@/components/_Icon/IconComponent";
import { IconTrash } from "@/components/_Icon/IconTrash";
import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButtonAntTitle";
import AdminComp_BoxTitle from "@/components/_ShareComponent/Admin/BoxTitlePage";
import { GridSpan_4_8 } from "@/components/_ShareComponent/GridSpan_4_8";
import { GridSpan_NewComponent } from "@/components/_ShareComponent/GridSpan_NewComponent";
import { MainColor } from "@/constants/color-palet";
import { ICON_SIZE_BUTTON } from "@/constants/constans-value";
import { useAuth } from "@/hooks/use-auth";
import {
apiAdminForumDeactivatePosting,
apiAdminForumListReportPostingById,
apiAdminForumPostingById,
} from "@/service/api-admin/api-admin-forum";
import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
import _ from "lodash";
import { useCallback, useState } from "react";
import { View } from "react-native";
import { Divider } from "react-native-paper";
import Toast from "react-native-toast-message";
import { Admin_ScreenForumDetailReportPosting } from "@/screens/Admin/Forum/ScreenForumDetailReportPosting";
export default function AdminForumReportPosting() {
const { user } = useAuth();
const { id } = useLocalSearchParams();
const [openDrawerPage, setOpenDrawerPage] = useState(false);
const [openDrawerAction, setOpenDrawerAction] = useState(false);
const [data, setData] = useState<any | null>(null);
const [listReport, setListReport] = useState<any[] | null>(null);
const [loadListReport, setLoadListReport] = useState(false);
const [selectedReport, setSelectedReport] = useState({
id: "",
username: "",
kategori: "",
keterangan: "",
deskripsi: "",
});
useFocusEffect(
useCallback(() => {
onLoadData();
}, [id])
);
const onLoadData = async () => {
try {
setLoadListReport(true);
const response = await apiAdminForumPostingById({
id: id as string,
});
const responseReport = await apiAdminForumListReportPostingById({
id: id as string,
});
if (response.success) {
setData(response.data);
}
if (responseReport.success) {
setListReport(responseReport.data);
}
} catch (error) {
console.log("[ERROR]", error);
} finally {
setLoadListReport(false);
}
};
return (
<>
<ViewWrapper
headerComponent={
<AdminBackButtonAntTitle
title="Report Posting"
rightComponent={
<ActionIcon
icon={<IconDot size={16} color={MainColor.darkblue} />}
onPress={() => setOpenDrawerPage(true)}
/>
}
/>
}
>
<BaseBox>
<StackCustom gap={"sm"}>
<GridSpan_NewComponent
text1={<TextCustom bold>Username</TextCustom>}
text2={<TextCustom>{data?.Author?.username || "-"}</TextCustom>}
/>
<GridSpan_NewComponent
text1={<TextCustom bold>Status</TextCustom>}
text2={
data && data?.ForumMaster_StatusPosting?.status ? (
<BadgeCustom
color={
data?.ForumMaster_StatusPosting?.status === "Open"
? MainColor.green
: MainColor.red
}
>
{data?.ForumMaster_StatusPosting?.status === "Open"
? "Open"
: "Close"}
</BadgeCustom>
) : (
<TextCustom>{"-"}</TextCustom>
)
}
/>
<GridSpan_NewComponent
text1={<TextCustom bold>Postingan</TextCustom>}
text2={<TextCustom>{data?.diskusi || "-"}</TextCustom>}
/>
</StackCustom>
</BaseBox>
<AdminComp_BoxTitle title="Daftar Report Posting" />
<StackCustom gap={"sm"}>
<GridSpan_NewComponent
text1={
<TextCustom bold align="center">
Aksi
</TextCustom>
}
text2={<TextCustom bold>Pelapor</TextCustom>}
text3={<TextCustom bold>Kategori Report</TextCustom>}
/>
<Divider />
{loadListReport ? (
<LoaderCustom />
) : _.isEmpty(listReport) ? (
<TextCustom align="center" color={"gray"}>
Belum ada report
</TextCustom>
) : (
listReport?.map((item: any, index: number) => (
<View key={index}>
<GridSpan_NewComponent
text1={
<CenterCustom>
<ActionIcon
icon={
<IconView size={ICON_SIZE_BUTTON} color="black" />
}
onPress={() => {
setOpenDrawerAction(true);
setSelectedReport({
id: item?.id,
username: item?.User?.username,
kategori: item?.ForumMaster_KategoriReport?.title,
keterangan:
item?.ForumMaster_KategoriReport?.deskripsi,
deskripsi: item?.deskripsi,
});
}}
/>
</CenterCustom>
}
text2={
<TextCustom truncate>
{item?.User?.username || "-"}
</TextCustom>
}
text3={
<TextCustom truncate={2}>
{item?.ForumMaster_KategoriReport?.title || "-"}
</TextCustom>
}
/>
<Divider />
</View>
))
)}
</StackCustom>
</ViewWrapper>
<DrawerCustom
isVisible={openDrawerPage}
closeDrawer={() => setOpenDrawerPage(false)}
height={"auto"}
>
<MenuDrawerDynamicGrid
data={[
{
icon: <IconTrash />,
label: "Hapus Posting",
value: "delete",
path: "",
color: MainColor.red,
},
]}
onPressItem={(item) => {
AlertDefaultSystem({
title: "Hapus Posting",
message: "Apakah Anda yakin ingin menghapus posting ini?",
textLeft: "Batal",
textRight: "Hapus",
onPressRight: async () => {
const response = await apiAdminForumDeactivatePosting({
id: id as string,
data: {
senderId: user?.id as string,
},
});
if (!response.success) {
Toast.show({
type: "error",
text1: "Posting gagal dihapus",
});
return;
}
setOpenDrawerPage(false);
Toast.show({
type: "success",
text1: "Posting berhasil dihapus",
});
router.back();
},
});
}}
/>
</DrawerCustom>
<DrawerCustom
isVisible={openDrawerAction}
closeDrawer={() => setOpenDrawerAction(false)}
height={"auto"}
>
<StackCustom>
<GridSpan_4_8
label={<TextCustom bold>Pelapor</TextCustom>}
value={<TextCustom>{selectedReport?.username || "-"}</TextCustom>}
/>
{selectedReport?.kategori && (
<>
<GridSpan_4_8
label={<TextCustom bold>Kategori Report</TextCustom>}
value={
<TextCustom>{selectedReport?.kategori || "-"}</TextCustom>
}
/>
<GridSpan_4_8
label={<TextCustom bold>Keterangan</TextCustom>}
value={
<TextCustom>{selectedReport?.keterangan || "-"}</TextCustom>
}
/>
</>
)}
{selectedReport?.deskripsi && (
<GridSpan_4_8
label={<TextCustom bold>Deskripsi</TextCustom>}
value={
<TextCustom>{selectedReport?.deskripsi || "-"}</TextCustom>
}
/>
)}
</StackCustom>
</DrawerCustom>
</>
);
export default function AdminForumDetailReportPosting() {
return <Admin_ScreenForumDetailReportPosting />;
}

View File

@@ -1,121 +1,5 @@
/* eslint-disable react-hooks/exhaustive-deps */
import {
ClickableCustom,
LoaderCustom,
SearchInput,
Spacing,
StackCustom,
TextCustom,
ViewWrapper,
} from "@/components";
import AdminComp_BoxTitle from "@/components/_ShareComponent/Admin/BoxTitlePage";
import AdminTitlePage from "@/components/_ShareComponent/Admin/TitlePage";
import { GridSpan_4_8 } from "@/components/_ShareComponent/GridSpan_4_8";
import { GridSpan_NewComponent } from "@/components/_ShareComponent/GridSpan_NewComponent";
import { apiAdminForum } from "@/service/api-admin/api-admin-forum";
import { router, useFocusEffect } from "expo-router";
import _ from "lodash";
import { useCallback, useState } from "react";
import { View } from "react-native";
import { Divider } from "react-native-paper";
import { Admin_ScreenForumPosting } from "@/screens/Admin/Forum/ScreenForumPosting";
export default function AdminForumPosting() {
const [list, setList] = useState<any | null>(null);
const [loadList, setLoadList] = useState(false);
const [search, setSearch] = useState("");
useFocusEffect(
useCallback(() => {
handlerLoadList();
}, [search])
);
const handlerLoadList = async () => {
try {
setLoadList(true);
const response = await apiAdminForum({
category: "posting",
search: search,
});
console.log("DATA", JSON.stringify(response, null, 2));
if (response.success) {
setList(response.data);
}
} catch (error) {
console.log("[ERROR]", error);
} finally {
setLoadList(false);
}
};
const rightComponent = (
<SearchInput
containerStyle={{ width: "100%", marginBottom: 0 }}
placeholder="Cari postingan"
value={search}
onChangeText={setSearch}
/>
);
return (
<>
<ViewWrapper headerComponent={<AdminTitlePage title="Forum" />}>
<AdminComp_BoxTitle title={"Posting"} rightComponent={rightComponent} />
<GridSpan_NewComponent
text1={<TextCustom bold truncate>Username</TextCustom>}
text2={<TextCustom bold truncate> Postingan</TextCustom>}
text3={<TextCustom bold align="center" truncate> Report Posting</TextCustom>}
text4={<TextCustom bold align="center" truncate> Komentar</TextCustom>}
/>
<Divider />
<Spacing />
<StackCustom>
{loadList ? (
<LoaderCustom />
) : _.isEmpty(list) ? (
<TextCustom align="center" color="gray">
Belum ada data
</TextCustom>
) : (
list?.map((item: any, index: number) => (
<View key={index}>
<ClickableCustom
onPress={() => {
router.push(`/admin/forum/${item.id}`);
}}
>
<GridSpan_NewComponent
text1={
<TextCustom truncate={1}>
{item?.Author?.username || "-"}
</TextCustom>
}
text2={
<TextCustom truncate>
{item?.diskusi || "-"}
</TextCustom>
}
text3={
<TextCustom align="center" truncate={2}>
{item?.reportPosting || "-"}
</TextCustom>
}
text4={
<TextCustom align="center" truncate={2}>
{item?.komentar || "-"}
</TextCustom>
}
/>
</ClickableCustom>
<Divider />
</View>
))
)}
</StackCustom>
</ViewWrapper>
</>
);
return <Admin_ScreenForumPosting />;
}

View File

@@ -1,137 +1,5 @@
/* eslint-disable react-hooks/exhaustive-deps */
import {
ActionIcon,
ClickableCustom,
LoaderCustom,
SearchInput,
Spacing,
StackCustom,
TextCustom,
ViewWrapper,
} from "@/components";
import { IconView } from "@/components/_Icon/IconComponent";
import AdminComp_BoxTitle from "@/components/_ShareComponent/Admin/BoxTitlePage";
import AdminTitleTable from "@/components/_ShareComponent/Admin/TableTitle";
import AdminTableValue from "@/components/_ShareComponent/Admin/TableValue";
import AdminTitlePage from "@/components/_ShareComponent/Admin/TitlePage";
import { GridSpan_NewComponent } from "@/components/_ShareComponent/GridSpan_NewComponent";
import { MainColor } from "@/constants/color-palet";
import { ICON_SIZE_BUTTON } from "@/constants/constans-value";
import { apiAdminForum } from "@/service/api-admin/api-admin-forum";
import { router, useFocusEffect } from "expo-router";
import _ from "lodash";
import { useCallback, useState } from "react";
import { View } from "react-native";
import { Divider } from "react-native-paper";
import { Admin_ScreenForumReportComment } from "@/screens/Admin/Forum/ScreenForumReportComment";
export default function AdminForumReportComment() {
const [listData, setListData] = useState<any[] | null>(null);
const [loadList, setLoadList] = useState<boolean>(false);
const [search, setSearch] = useState<string>("");
useFocusEffect(
useCallback(() => {
onLoadData();
}, [search])
);
const onLoadData = async () => {
try {
setLoadList(true);
const response = await apiAdminForum({
category: "report_comment",
search: search,
});
if (response.success) {
setListData(response.data);
}
} catch (error) {
console.log("[ERROR]", error);
} finally {
setLoadList(false);
}
};
const rightComponent = (
<SearchInput
containerStyle={{ width: "100%", marginBottom: 0 }}
placeholder="Cari Komentar"
value={search}
onChangeText={setSearch}
/>
);
return (
<>
<ViewWrapper headerComponent={<AdminTitlePage title="Forum" />}>
<AdminComp_BoxTitle
title="Report Komentar"
rightComponent={rightComponent}
/>
<GridSpan_NewComponent
text1={
<TextCustom bold truncate>
Pelapor
</TextCustom>
}
text2={
<TextCustom bold truncate>
Komentar
</TextCustom>
}
text3={
<TextCustom bold truncate>
Jenis Laporan
</TextCustom>
}
/>
<Divider />
<Spacing />
<StackCustom gap={"lg"}>
{loadList ? (
<LoaderCustom />
) : _.isEmpty(listData) ? (
<TextCustom align="center" color="gray">
Belum ada data
</TextCustom>
) : (
listData?.map((item: any, index: number) => (
<View key={index}>
<ClickableCustom
onPress={() => {
router.push(
`/admin/forum/${item?.Forum_Komentar?.id}/list-report-comment`
);
}}
>
<GridSpan_NewComponent
text1={
<TextCustom truncate={1}>
{item?.User?.username || "-"}
</TextCustom>
}
text2={
<TextCustom truncate={2}>
{item?.Forum_Komentar?.komentar || "-"}
</TextCustom>
}
text3={
<TextCustom truncate={2}>
{item?.ForumMaster_KategoriReport?.title || "-"}
</TextCustom>
}
/>
</ClickableCustom>
<Spacing />
<Divider />
</View>
))
)}
</StackCustom>
</ViewWrapper>
</>
);
return <Admin_ScreenForumReportComment />;
}

View File

@@ -1,124 +1,5 @@
/* eslint-disable react-hooks/exhaustive-deps */
import {
ActionIcon,
ClickableCustom,
Divider,
LoaderCustom,
SearchInput,
StackCustom,
TextCustom,
ViewWrapper,
} from "@/components";
import { IconView } from "@/components/_Icon/IconComponent";
import AdminComp_BoxTitle from "@/components/_ShareComponent/Admin/BoxTitlePage";
import AdminTitleTable from "@/components/_ShareComponent/Admin/TableTitle";
import AdminTableValue from "@/components/_ShareComponent/Admin/TableValue";
import AdminTitlePage from "@/components/_ShareComponent/Admin/TitlePage";
import { GridSpan_NewComponent } from "@/components/_ShareComponent/GridSpan_NewComponent";
import { MainColor } from "@/constants/color-palet";
import { ICON_SIZE_BUTTON } from "@/constants/constans-value";
import { apiAdminForum } from "@/service/api-admin/api-admin-forum";
import { router, useFocusEffect } from "expo-router";
import _ from "lodash";
import { useCallback, useState } from "react";
import { View } from "react-native";
import { Admin_ScreenForumReportPosting } from "@/screens/Admin/Forum/ScreenForumReportPosting";
export default function AdminForumReportPosting() {
const [listData, setListData] = useState<any[] | null>(null);
const [loadList, setLoadList] = useState<boolean>(false);
const [search, setSearch] = useState<string>("");
useFocusEffect(
useCallback(() => {
onLoadData();
}, [search])
);
const onLoadData = async () => {
try {
setLoadList(true);
const response = await apiAdminForum({
category: "report_posting",
search: search,
});
if (response.success) {
setListData(response.data);
}
} catch (error) {
console.log("[ERROR]", error);
} finally {
setLoadList(false);
}
};
const rightComponent = (
<SearchInput
containerStyle={{ width: "100%", marginBottom: 0 }}
placeholder="Cari Postingan"
value={search}
onChangeText={setSearch}
/>
);
return (
<>
<ViewWrapper headerComponent={<AdminTitlePage title="Forum" />}>
<AdminComp_BoxTitle
title="Report Posting"
rightComponent={rightComponent}
/>
<GridSpan_NewComponent
text1={
<TextCustom bold truncate>
Pelapor
</TextCustom>
}
text2={
<TextCustom bold truncate>
Postingan
</TextCustom>
}
/>
<Divider />
<StackCustom>
{loadList ? (
<LoaderCustom />
) : _.isEmpty(listData) ? (
<TextCustom align="center" color="gray">
Belum ada data
</TextCustom>
) : (
listData?.map((item: any, index: number) => (
<View key={index}>
<ClickableCustom
onPress={() => {
router.push(
`/admin/forum/${item?.Forum_Posting?.id}/list-report-posting`
);
}}
>
<GridSpan_NewComponent
text1={
<TextCustom truncate={1}>
{item?.User?.username || "-"}
</TextCustom>
}
text2={
<TextCustom truncate={1}>
{item?.Forum_Posting?.diskusi || "-"}
</TextCustom>
}
/>
</ClickableCustom>
<Divider />
</View>
))
)}
</StackCustom>
</ViewWrapper>
</>
);
return <Admin_ScreenForumReportPosting />;
}

View File

@@ -20,6 +20,8 @@ import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButt
import AdminButtonReject from "@/components/_ShareComponent/Admin/ButtonReject";
import AdminButtonReview from "@/components/_ShareComponent/Admin/ButtonReview";
import { GridSpan_4_8 } from "@/components/_ShareComponent/GridSpan_4_8";
import GridTwoView from "@/components/_ShareComponent/GridTwoView";
import CustomSkeleton from "@/components/_ShareComponent/SkeletonCustom";
import ReportBox from "@/components/Box/ReportBox";
import { MainColor } from "@/constants/color-palet";
import { ICON_SIZE_BUTTON } from "@/constants/constans-value";
@@ -28,6 +30,7 @@ import {
apiAdminInvestmentDetailById,
} from "@/service/api-admin/api-admin-investment";
import { colorBadgeStatus } from "@/utils/colorBadge";
import { countDownAndCondition } from "@/utils/countDownAndCondition";
import { formatCurrencyDisplay } from "@/utils/formatCurrencyDisplay";
import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
import _ from "lodash";
@@ -40,91 +43,41 @@ export default function AdminInvestmentDetail() {
const [data, setData] = React.useState<any | null>(null);
const [isLoading, setLoading] = React.useState(false);
const [remind, setRemind] = React.useState({
sisa: 0,
reminder: false,
});
useFocusEffect(
React.useCallback(() => {
onLoadData();
}, [id])
}, [id]),
);
const onLoadData = async () => {
try {
const response = await apiAdminInvestmentDetailById({ id: id as string });
// console.log("[GETONE INVEST]", JSON.stringify(response, null, 2));
if (response.success) {
setData(response.data);
const duration = response?.data?.MasterPencarianInvestor?.name;
const publishTime = response?.data?.countDown;
const countDown = countDownAndCondition({
duration: duration,
publishTime: publishTime
});
setRemind({
sisa: countDown.durationDay,
reminder: countDown.reminder,
});
}
} catch (error) {
console.log(error);
console.log("Error", error);
}
};
const listData = [
{
label: "Username",
value: (data && data?.author?.username) || "-",
},
{
label: "Judul",
value: (data && data?.title) || "-",
},
{
label: "Status",
value:
data && data?.MasterStatusInvestasi?.name ? (
<BadgeCustom
color={colorBadgeStatus({
status: data?.MasterStatusInvestasi?.name as string,
})}
>
{_.startCase(data?.MasterStatusInvestasi?.name as string)}
</BadgeCustom>
) : (
"-"
),
},
{
label: "Dana Dibutuhkan",
value: `Rp. ${
(data && data?.targetDana && formatCurrencyDisplay(data?.targetDana)) ||
"-"
}`,
},
{
label: "Harga Perlembar",
value: `Rp. ${
(data &&
data?.hargaLembar &&
formatCurrencyDisplay(data?.hargaLembar)) ||
"-"
}`,
},
{
label: "Total Lembar",
value:
(data &&
data?.totalLembar &&
formatCurrencyDisplay(data?.totalLembar)) ||
"-",
},
{
label: "ROI",
value: `${(data && data?.roi && data?.roi) || 0} %`,
},
{
label: "Pembagian Deviden",
value: (data && data?.MasterPembagianDeviden?.name) + " bulan" || "-",
},
{
label: "Jadwal Pembagian",
value: (data && data?.MasterPeriodeDeviden?.name) || "-",
},
{
label: "Pencarian Investor",
value: (data && data?.MasterPencarianInvestor?.name) + " hari" || "-",
},
];
const handlerSubmitPublish = async () => {
try {
setLoading(true);
@@ -134,7 +87,6 @@ export default function AdminInvestmentDetail() {
data: data,
});
// console.log("[GET ON INVEST]", JSON.stringify(response, null, 2));
if (!response.success) {
Toast.show({
type: "error",
@@ -164,6 +116,16 @@ export default function AdminInvestmentDetail() {
/>
);
if (!data) {
return (
<>
<ViewWrapper>
<CustomSkeleton height={200} />
</ViewWrapper>
</>
);
}
return (
<>
<ViewWrapper
@@ -177,8 +139,8 @@ export default function AdminInvestmentDetail() {
{status === "publish" && (
<BaseBox>
<ProgressCustom
label={data && `${data.progress}%` || "0%"}
value={data && data.progress || 0}
label={(data && `${data.progress}%`) || "0%"}
value={(data && data.progress) || 0}
size="lg"
/>
<Spacing />
@@ -187,7 +149,8 @@ export default function AdminInvestmentDetail() {
label={<TextCustom bold>Sisa Saham</TextCustom>}
value={
<TextCustom>
{data && formatCurrencyDisplay(data && data?.sisaLembar)} lembar
{data && formatCurrencyDisplay(data && data?.sisaLembar)}{" "}
lembar
</TextCustom>
}
/>
@@ -206,21 +169,23 @@ export default function AdminInvestmentDetail() {
<BaseBox>
<StackCustom>
<DummyLandscapeImage imageId={data?.imageId} />
{listData.map((item, i) => (
<GridSpan_4_8
key={i}
label={<TextCustom bold>{item.label}</TextCustom>}
value={<TextCustom>{item.value}</TextCustom>}
/>
))}
{listData({ data: data, reminder: remind.reminder })?.map(
(item, i) => (
<GridSpan_4_8
key={i}
label={<TextCustom bold>{item.label}</TextCustom>}
value={<TextCustom>{item.value}</TextCustom>}
/>
),
)}
</StackCustom>
</BaseBox>
<BaseBox>
<StackCustom>
<GridSpan_4_8
label={<TextCustom bold>File Prospektus</TextCustom>}
value={
<GridTwoView
leftItem={<TextCustom bold>File Prospektus</TextCustom>}
rightItem={
<ButtonCustom
iconLeft={
<IconProspectus
@@ -230,7 +195,7 @@ export default function AdminInvestmentDetail() {
}
onPress={() => {
router.push(
`/(application)/(file)/${data?.prospektusFileId}`
`/(application)/(file)/${data?.prospektusFileId}`,
);
}}
>
@@ -238,9 +203,10 @@ export default function AdminInvestmentDetail() {
</ButtonCustom>
}
/>
<GridSpan_4_8
label={<TextCustom bold>File Dokumen</TextCustom>}
value={
<GridTwoView
leftItem={<TextCustom bold>File Dokumen</TextCustom>}
rightItem={
<StackCustom>
{_.isEmpty(data?.DokumenInvestasi) ? (
<TextCustom align="center">-</TextCustom>
@@ -259,7 +225,7 @@ export default function AdminInvestmentDetail() {
}
onPress={() => {
router.push(
`/(application)/(file)/${item?.fileId}`
`/(application)/(file)/${item?.fileId}`,
);
}}
>
@@ -299,8 +265,8 @@ export default function AdminInvestmentDetail() {
onReject={() => {
router.push(
`/admin/investment/${id}/reject-input?status=${_.lowerCase(
data?.MasterStatusInvestasi?.name
)}`
data?.MasterStatusInvestasi?.name,
)}`,
);
}}
/>
@@ -343,3 +309,67 @@ export default function AdminInvestmentDetail() {
</>
);
}
const listData = ({ data, reminder }: { data: any; reminder: boolean }) => [
{
label: "Username",
value: (data && data?.author?.username) || "-",
},
{
label: "Judul",
value: (data && data?.title) || "-",
},
{
label: "Status",
value:
data && data?.MasterStatusInvestasi?.name ? (
<BadgeCustom
color={colorBadgeStatus({
status: reminder ? "periode berakhir" : "publish",
})}
>
{reminder
? "Periode Berakhir"
: _.startCase(data?.MasterStatusInvestasi?.name as string)}
</BadgeCustom>
) : (
"-"
),
},
{
label: "Dana Dibutuhkan",
value: `Rp. ${
(data && data?.targetDana && formatCurrencyDisplay(data?.targetDana)) ||
"-"
}`,
},
{
label: "Harga Perlembar",
value: `Rp. ${
(data && data?.hargaLembar && formatCurrencyDisplay(data?.hargaLembar)) ||
"-"
}`,
},
{
label: "Total Lembar",
value:
(data && data?.totalLembar && formatCurrencyDisplay(data?.totalLembar)) ||
"-",
},
{
label: "ROI",
value: `${(data && data?.roi && data?.roi) || 0} %`,
},
{
label: "Pembagian Deviden",
value: (data && data?.MasterPembagianDeviden?.name) + " bulan" || "-",
},
{
label: "Jadwal Pembagian",
value: (data && data?.MasterPeriodeDeviden?.name) || "-",
},
{
label: "Pencarian Investor",
value: (data && data?.MasterPencarianInvestor?.name) + " hari" || "-",
},
];

View File

@@ -1,21 +1,22 @@
/* eslint-disable react-hooks/exhaustive-deps */
import {
AlertDefaultSystem,
BadgeCustom,
BaseBox,
ButtonCustom,
Spacing,
StackCustom,
TextCustom,
ViewWrapper,
AlertDefaultSystem,
BadgeCustom,
BaseBox,
ButtonCustom,
Spacing,
StackCustom,
TextCustom,
ViewWrapper,
} from "@/components";
import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButtonAntTitle";
import { GridSpan_4_8 } from "@/components/_ShareComponent/GridSpan_4_8";
import GridTwoView from "@/components/_ShareComponent/GridTwoView";
import { MainColor } from "@/constants/color-palet";
import { useAuth } from "@/hooks/use-auth";
import {
apiAdminInvestmentGetOneInvoiceById,
apiAdminInvestmentUpdateInvoice,
apiAdminInvestmentGetOneInvoiceById,
apiAdminInvestmentUpdateInvoice,
} from "@/service/api-admin/api-admin-investment";
import { colorBadgeTransaction } from "@/utils/colorBadge";
import { dateTimeView } from "@/utils/dateTimeView";
@@ -25,6 +26,7 @@ import { useCallback, useState } from "react";
import Toast from "react-native-toast-message";
export default function AdminInvestmentTransactionDetail() {
const { user } = useAuth();
const { id } = useLocalSearchParams();
const [data, setData] = useState<any | null>(null);
const [isLoading, setLoading] = useState<boolean>(false);
@@ -32,7 +34,7 @@ export default function AdminInvestmentTransactionDetail() {
useFocusEffect(
useCallback(() => {
onLoadData();
}, [id])
}, [id]),
);
const onLoadData = async () => {
@@ -40,7 +42,6 @@ export default function AdminInvestmentTransactionDetail() {
const response = await apiAdminInvestmentGetOneInvoiceById({
id: id as string,
});
// console.log("[RESPONSE]", JSON.stringify(response, null, 2));
if (response.success) {
setData(response.data);
}
@@ -59,7 +60,7 @@ export default function AdminInvestmentTransactionDetail() {
value: (data && data?.MasterBank?.namaBank) || "-",
},
{
label: "Jumlah Investasi",
label: "Nominal",
value: (data && `Rp. ${formatCurrencyDisplay(data?.nominal)}`) || "-",
},
{
@@ -92,7 +93,7 @@ export default function AdminInvestmentTransactionDetail() {
<ButtonCustom
onPress={() =>
router.push(
`/(application)/(image)/preview-image/${data?.imageId}`
`/(application)/(image)/preview-image/${data?.imageId}`,
)
}
>
@@ -109,6 +110,13 @@ export default function AdminInvestmentTransactionDetail() {
}: {
category: "accept" | "deny";
}) => {
if (!user?.id) {
Toast.show({
type: "error",
text1: "Gagal update status transaksi",
});
return;
}
try {
setLoading(true);
const response = await apiAdminInvestmentUpdateInvoice({
@@ -117,11 +125,10 @@ export default function AdminInvestmentTransactionDetail() {
data: {
investasiId: data?.investasiId,
lembarTerbeli: data?.lembarTerbeli,
senderId: user?.id as any,
},
});
// console.log("[RESPONSE SUBMIT]", JSON.stringify(response, null, 2));
if (!response.success) {
Toast.show({
type: "error",
@@ -151,8 +158,9 @@ export default function AdminInvestmentTransactionDetail() {
spanRight={6}
styleLeft={{ paddingRight: 10 }}
styleRight={{ paddingLeft: 10 }}
leftIcon={
leftItem={
<ButtonCustom
disabled={isLoading}
isLoading={isLoading}
backgroundColor={MainColor.red}
textColor="white"
@@ -173,8 +181,9 @@ export default function AdminInvestmentTransactionDetail() {
Tolak
</ButtonCustom>
}
rightIcon={
rightItem={
<ButtonCustom
disabled={isLoading}
isLoading={isLoading}
onPress={() => {
AlertDefaultSystem({
@@ -198,8 +207,8 @@ export default function AdminInvestmentTransactionDetail() {
} else if (data?.StatusInvoice?.name === "Gagal") {
return (
<>
<ButtonCustom textColor="red" onPress={() => router.back()}>
Gagal
<ButtonCustom disabled onPress={() => router.back()}>
Transaksi telah gagal
</ButtonCustom>
</>
);

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