Compare commits

..

34 Commits

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

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

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

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

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

### No Issue
2025-11-04 12:13:49 +08:00
867e82c6fa Merge pull request 'Admin : Investasi integarsi API' (#6) from api-admin/31-oct-25 into main
Reviewed-on: http://wibugit.wibudev.com/wibu/hipmi-mobile/pulls/6
2025-11-03 11:45:34 +08:00
f9f996f195 Admin : Investasi integarsi API
### NO Issue
2025-11-03 11:41:31 +08:00
98394309e6 Merge pull request 'Integrasi API: Investment & Admin Investment' (#5) from api-admin/30-oct-25 into main
Reviewed-on: http://wibugit.wibudev.com/wibu/hipmi-mobile/pulls/5
2025-10-30 17:43:51 +08:00
4625831377 Integrasi API: Admin Investasi
Fix:
- app/(application)/(user)/investment/(tabs)/index.tsx
- app/(application)/admin/investment/[id]/[status]/transaction-detail.tsx
- app/(application)/admin/investment/[id]/list-of-investor.tsx
- screens/Invesment/BoxBerandaSection.tsx
- screens/Invesment/DetailDataPublishSection.tsx
- service/api-admin/api-admin-investment.ts

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

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

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

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

### No Issue
2025-10-30 15:13:33 +08:00
f9d9b5fbaa Merge pull request 'Integrasi API: Donation & Admin Donation' (#4) from api-admin/29-oct-25 into main
Reviewed-on: http://wibugit.wibudev.com/wibu/hipmi-mobile/pulls/4
2025-10-29 17:38:03 +08:00
b3209dc7ee Integrasi API: Donation & Admin Donation
Fix:
- app/(application)/(user)/donation/[id]/fund-disbursement.tsx
- app/(application)/(user)/donation/[id]/list-of-donatur.tsx
- app/(application)/admin/donation/[id]/[status]/index.tsx
- app/(application)/admin/donation/[id]/detail-disbursement-of-funds.tsx
- app/(application)/admin/donation/[id]/disbursement-of-funds.tsx
- app/(application)/admin/donation/[id]/list-disbursement-of-funds.tsx
- service/api-admin/api-admin-donation.ts
- service/api-client/api-donation.ts
- utils/pickFile.ts: Sudah bisa memilih ukuran crop tapi hanya di android

### No issue
2025-10-29 17:35:18 +08:00
31c1b35173 Merge pull request 'Integrasi API: Donation & Admin Donation' (#3) from api-admin/28-oct-25 into main
Reviewed-on: http://wibugit.wibudev.com/wibu/hipmi-mobile/pulls/3
2025-10-28 17:51:23 +08:00
1e1b18f860 Integrasi API: Donation & Admin Donation
Fix:
- app/(application)/(user)/donation/[id]/(transaction-flow)/[invoiceId]/invoice.tsx
- app/(application)/(user)/donation/[id]/index.tsx
- app/(application)/admin/donation/[id]/[status]/index.tsx
- app/(application)/admin/donation/[id]/[status]/transaction-detail.tsx
- app/(application)/admin/donation/[id]/list-of-donatur.tsx
- components/Select/SelectCustom.tsx
- context/AuthContext.tsx
- screens/Donation/BoxPublish.tsx
- screens/Donation/ProgressSection.tsx
- service/api-admin/api-admin-donation.ts

### NO Issue
2025-10-28 17:45:13 +08:00
de0280367f Merge pull request 'Integrasi API: Donation Admin' (#2) from api-admin/27-oct-25 into main
Reviewed-on: http://wibugit.wibudev.com/wibu/hipmi-mobile/pulls/2
2025-10-28 10:21:39 +08:00
5d4328a139 Integrasi API: Donation Admin
Add:
-  screens/Admin/Donation/funDonationUpdateStatus.ts
-  utils/countDownAndCondition.ts

Fix:
- app/(application)/(user)/donation/[id]/index.tsx
- app/(application)/admin/donation/[id]/[status]/index.tsx
- app/(application)/admin/donation/[id]/list-of-donatur.tsx
- app/(application)/admin/donation/[id]/reject-input.tsx
- app/(application)/admin/donation/index.tsx
- app/(application)/admin/event/[id]/[status]/index.tsx
- app/(application)/admin/voting/[id]/[status]/index.tsx
- screens/Admin/Donation/BoxOfDonationStory.tsx
- screens/Donation/BoxPublish.tsx
- screens/Donation/ComponentBoxDetailData.tsx
- service/api-admin/api-admin-donation.ts
- service/api-client/api-master.ts
- utils/colorBadge.ts
git add . && git commit -m
2025-10-28 10:19:47 +08:00
c8b14b816f Merge pull request 'New repo mobile after delete !' (#1) from api/24-oct-25 into main
Reviewed-on: http://wibugit.wibudev.com/wibu/hipmi-mobile/pulls/1
2025-10-27 11:32:15 +08:00
125bf16605 Update new github 2025-10-27 10:51:57 +08:00
73a803f2e8 Update new github 2025-10-27 10:49:31 +08:00
1bcd1a044f Integrasi API: Event Type
Add:
- utils/colorActivationForBadge.ts

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

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

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

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

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

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

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

### No Issue
2025-10-21 16:52:17 +08:00
faf0f36e53 UI Fix: Pada tampilan ios bagian button ada yang ta terlihat dan sudah di perbaiki
### No Issue
2025-10-21 11:17:37 +08:00
160 changed files with 8698 additions and 1549 deletions

View File

@@ -92,8 +92,8 @@ android {
applicationId 'com.bip.hipmimobileapp'
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 1
versionName "1.0.0"
versionCode 2
versionName "1.0.1"
buildConfigField "String", "REACT_NATIVE_RELEASE_LEVEL", "\"${findProperty('reactNativeReleaseLevel') ?: 'stable'}\""
}
@@ -180,3 +180,5 @@ dependencies {
implementation jscFlavor
}
}
apply plugin: 'com.google.gms.google-services'

View File

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

View File

@@ -29,6 +29,12 @@
<data android:scheme="hipmimobile"/>
<data android:scheme="exp+hipmi-mobile"/>
</intent-filter>
<intent-filter android:autoVerify="true" data-generated="true">
<action android:name="android.intent.action.VIEW"/>
<data android:scheme="https" android:host="cld-dkr-staging-hipmi.wibudev.com" android:pathPrefix="/"/>
<category android:name="android.intent.category.BROWSABLE"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity>
</application>
</manifest>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 97 KiB

After

Width:  |  Height:  |  Size: 154 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 151 KiB

After

Width:  |  Height:  |  Size: 231 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.3 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.0 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.3 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 30 KiB

View File

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

View File

@@ -6,6 +6,7 @@ buildscript {
mavenCentral()
}
dependencies {
classpath 'com.google.gms:google-services:4.4.1'
classpath('com.android.tools.build:gradle')
classpath('com.facebook.react:react-native-gradle-plugin')
classpath('org.jetbrains.kotlin:kotlin-gradle-plugin')

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,554 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
/* eslint-disable react-hooks/exhaustive-deps */
import {
BaseBox,
ButtonCustom,
CenterCustom,
LoaderCustom,
StackCustom,
TextCustom,
ViewWrapper,
} from "@/components";
import { AccentColor, MainColor } from "@/constants/color-palet";
import { useAuth } from "@/hooks/use-auth";
import {
apiEventConfirmationAction,
apiEventGetConfirmation,
apiEventJoin,
} from "@/service/api-client/api-event";
import { Ionicons } from "@expo/vector-icons";
import dayjs from "dayjs";
import isBetween from "dayjs/plugin/isBetween";
import {
Redirect,
router,
Stack,
useFocusEffect,
useLocalSearchParams,
} from "expo-router";
import React, { useCallback, useState } from "react";
import { View } from "react-native";
import Toast from "react-native-toast-message";
// Extend Day.js dengan plugin isBetween
dayjs.extend(isBetween);
interface DataEvent {
id: string;
title: string;
tanggal: Date;
tanggalSelesai: Date;
lokasi: string;
Author: {
id: string;
username: string;
Profile: {
id: string;
name: string;
};
};
}
export default function UserEventConfirmation() {
const { token } = useAuth();
const { id, userId: authorId } = useLocalSearchParams();
const { user } = useAuth();
const [data, setData] = useState<DataEvent | null>(null);
const [peserta, setPeserta] = useState<boolean | null>(null);
const [konfirmasi, setKonfirmasi] = useState<boolean | null>(null);
useFocusEffect(
useCallback(() => {
checkTokenAndDataParticipants() || console.log("Token is null");
}, [token, id, user?.id])
);
const checkTokenAndDataParticipants = async () => {
if (!token) {
return <Redirect href={`/`} />;
}
try {
const response = await apiEventGetConfirmation({
id: id as string,
userId: user?.id as string,
});
if (response.success) {
setData(response.data?.dataEvent);
setPeserta(response.data?.peserta);
setKonfirmasi(response.data?.kehadiran);
}
} catch (error) {
console.log("[ERROR CONFIRMATION]", error);
}
};
const handlerReturn = () => {
const now = dayjs(); // asumsi: UTC, sesuai dengan API
// --- [1] Loading & Data tidak ditemukan ---
if (data === undefined || data === null) {
if (peserta === null && konfirmasi === null) {
return <LoaderCustom />;
}
return (
<BaseBox>
<TextCustom bold align="center" size={"large"}>
Data Tidak Ditemukan
</TextCustom>
<BackToOtherPath path="home" />
</BaseBox>
);
}
// --- [2] Ambil waktu event dari `data` ---
const eventStart = dayjs(data.tanggal);
const eventEnd = dayjs(data.tanggalSelesai);
// --- [3] Definisikan jendela konfirmasi: 1 jam sebelum mulai → 1 jam setelah selesai ---
const confirmationStart = eventStart.subtract(1, "hour");
const confirmationEnd = eventEnd.add(1, "hour");
const isWithinConfirmationWindow = now.isBetween(
confirmationStart,
confirmationEnd,
null,
"[]"
);
// --- [4] Status waktu event (untuk pesan UI) ---
const isBeforeEvent = now.isBefore(eventStart);
const isAfterEvent = now.isAfter(eventEnd);
const isDuringEvent = !isBeforeEvent && !isAfterEvent;
// --- [5] Handle berdasarkan waktu dan status peserta/konfirmasi ---
// 🟢 Acara sudah selesai
if (isAfterEvent) {
if (peserta === false) {
return (
<TamplateBox data={data}>
<TamplateText text="Event telah selesai, sehingga konfirmasi kehadiran sudah tidak dapat dilakukan. Terima kasih atas perhatian dan minat Anda. Kami berharap dapat bertemu di acara kami berikutnya." />
<BackToOtherPath
path="event"
id={data.id}
isAfterEvent={isAfterEvent}
/>
</TamplateBox>
);
}
return (
<TamplateBox data={data}>
<TamplateText
text={`Event telah selesai, anda terdaftar sebagai peserta dan ${
konfirmasi
? "Anda telah mengonfirmasi kehadiran."
: "Anda tidak mengonfirmasi kehadiran."
} Terima kasih atas perhatian dan minat Anda. Kami berharap dapat bertemu di acara kami berikutnya.`}
/>
<BackToOtherPath
path="event"
id={data.id}
isAfterEvent={isAfterEvent}
/>
</TamplateBox>
);
}
// 🔵 Acara belum mulai & belum terdaftar
if (isBeforeEvent) {
if (peserta === false) {
return (
<NotStarted_And_UserNotParticipan
id={data.id}
userId={user?.id as string}
data={data}
/>
);
}
// Peserta sudah daftar → cek apakah sudah boleh konfirmasi
if (isWithinConfirmationWindow && peserta === true) {
if (konfirmasi === false) {
return (
<UserParticipan_And_DuringEvent
id={data.id}
userId={user?.id as string}
data={data}
/>
);
}
return (
<TamplateBox data={data}>
<TamplateText text="Terimakasih telah mengonfirmasi kehadiran. Silahkan lihat peserta lain pada halaman event atau kembali ke halaman home. Selamat menikmati acara dan selamat berpartisipasi." />
<BackToOtherPath
path="event"
id={data.id}
isAfterEvent={isAfterEvent}
/>
</TamplateBox>
);
}
return (
<TamplateBox data={data}>
<TamplateText text="Anda telah terdaftar sebagai peserta pada Event ini. Konfirmasi kehadiran dibuka 1 jam sebelum acara dimulai." />
<BackToOtherPath
path="event"
id={data.id}
isAfterEvent={isAfterEvent}
/>
</TamplateBox>
);
}
// 🟡 Acara sedang berlangsung & belum terdaftar
if (isDuringEvent) {
if (peserta === false) {
return (
<UserNotParticipan_And_DuringEvent
id={data.id}
userId={user?.id as string}
data={data}
/>
);
}
if (peserta === true) {
if (isWithinConfirmationWindow) {
if (konfirmasi === false) {
return (
<TamplateBox data={data}>
<TamplateText text="Konfirmasi Kehadiran" />
</TamplateBox>
);
}
return (
<TamplateBox data={data}>
<TamplateText text="Anda telah mengonfirmasi kehadiran dalam event ini. Terima kasih dan selamat menikmati acara. Have fun!" />
<BackToOtherPath
path="event"
id={data.id}
isAfterEvent={isAfterEvent}
/>
</TamplateBox>
);
}
// Ini sangat jarang terjadi selama event berlangsung, tapi aman
return (
<TamplateBox data={data}>
<TamplateText text="Konfirmasi kehadiran tidak tersedia saat ini." />
<BackToOtherPath path="home" />
</TamplateBox>
);
}
}
// 🛑 Fallback aman
return (
<BaseBox>
<StackCustom>
<TamplateText text="Terjadi kesalahan tak terduga pada logika waktu." />
<BackToOtherPath path="home" />
</StackCustom>
</BaseBox>
);
};
return (
<>
<Stack.Screen
options={{
title: "Konfirmasi Event",
// headerLeft: () => (
// <Ionicons
// name="arrow-back"
// size={20}
// color={MainColor.yellow}
// onPress={() =>
// router.navigate("/(application)/(user)/event/create")
// }
// />
// ),
}}
/>
<ViewWrapper>{handlerReturn()}</ViewWrapper>
</>
);
}
const TamplateBox = ({
data,
children,
}: {
data: DataEvent;
children: React.ReactNode;
}) => {
return (
<>
<CenterCustom>
<BaseBox>
<StackCustom gap={"lg"}>
<StackCustom gap={"sm"}>
<TextCustom bold align="center" size={"large"}>
{data?.title}
</TextCustom>
<View
style={{
flexDirection: "row",
justifyContent: "center",
// backgroundColor: AccentColor.blue,
// borderColor: AccentColor.blue,
// borderWidth: 1,
// borderRadius: 4,
// padding: 3
}}
>
<TextCustom align="center" size="small" bold>
{dayjs(data?.tanggal).format("DD MMM YYYY: HH:mm")}
</TextCustom>
<TextCustom align="center" size="small" bold>
{" "}
-{" "}
</TextCustom>
<TextCustom align="center" size="small" bold>
{dayjs(data?.tanggalSelesai).format("DD MMM YYYY: HH:mm")}
</TextCustom>
</View>
</StackCustom>
{children}
</StackCustom>
</BaseBox>
</CenterCustom>
</>
);
};
const TamplateText = ({ text }: { text: React.ReactNode }) => {
return (
<>
<TextCustom align="center">{text}</TextCustom>
</>
);
};
type BackToOtherPathProps =
| { path: "home" | "beranda-event"; id?: never; isAfterEvent?: never }
| { path: "event"; id: string; isAfterEvent: boolean };
const BackToOtherPath = ({ path, id, isAfterEvent }: BackToOtherPathProps) => {
return (
<>
{path === "home" ? (
<ButtonCustom
onPress={() => {
router.replace("/(application)/home");
}}
>
Home
</ButtonCustom>
) : (
<View
style={{
flexDirection: "row",
gap: 10,
justifyContent: "center",
}}
>
<ButtonCustom
onPress={() => {
router.replace("/(application)/home");
}}
>
Home
</ButtonCustom>
<ButtonCustom
onPress={() => {
if (path === "event") {
if (isAfterEvent) {
router.push(`/(application)/(user)/event/${id}/history`);
} else {
router.push(`/(application)/(user)/event/${id}/publish`);
}
} else if (path === "beranda-event") {
router.push(`/(application)/(user)/event`);
} else {
console.log("[PATH]", path);
}
}}
>
Lihat {path === "event" ? "Event" : "Beranda Event"}
</ButtonCustom>
</View>
)}
</>
);
};
// 🔵 Acara belum mulai & belum terdaftar
const NotStarted_And_UserNotParticipan = ({
id,
userId,
data,
}: {
id: string;
userId: string;
data: DataEvent;
}) => {
const [isLoading, setIsLoading] = useState<boolean>(false);
const handlerJoinEvent = async () => {
try {
setIsLoading(true);
const response = await apiEventJoin({
id: id as string,
userId: userId as string,
});
if (!response.success) {
Toast.show({
type: "error",
text1: "Anda gagal join",
});
return;
}
Toast.show({
type: "success",
text1: "Anda berhasil join",
});
router.navigate(`/(application)/(user)/event/${id}/publish`);
} catch (error) {
console.log("[ERROR JOIN EVENT]", error);
} finally {
setIsLoading(false);
}
};
return (
<>
<TamplateBox data={data}>
<TamplateText text="Anda belum terdaftar sebagai peserta & Event belum dimulai. Silahkan daftarkan diri anda terlebih dahulu" />
<ButtonCustom onPress={handlerJoinEvent} isLoading={isLoading}>
Join
</ButtonCustom>
</TamplateBox>
</>
);
};
// 🟡 ZONA ACARA BERLANGSUNG
// Acara sedang berlangsung & belum terdaftar & user harus join dan konfirmasi
const UserNotParticipan_And_DuringEvent = ({
id,
userId,
data,
}: {
id: string;
userId: string;
data: DataEvent;
}) => {
const [isLoading, setIsLoading] = useState<boolean>(false);
const handlerSubmit = async () => {
try {
setIsLoading(true);
const response = await apiEventConfirmationAction({
id: id as string,
userId: userId as string,
category: "join_and_confirm",
});
if (!response.success) {
Toast.show({
type: "error",
text1: "Anda gagal join & konfirmasi",
});
return;
}
Toast.show({
type: "success",
text1: "Anda berhasil join & konfirmasi",
});
router.navigate(`/(application)/(user)/event/${id}/publish`);
} catch (error) {
console.log("[ERROR JOIN & CONFIRMATION EVENT]", error);
} finally {
setIsLoading(false);
}
};
return (
<>
<TamplateBox data={data}>
<TamplateText text="Anda belum terdaftar sebagai peserta & Event sedang berlangsung. Silahkan daftarkan diri anda & Konfirmasi kehadiran" />
<ButtonCustom onPress={() => handlerSubmit()} isLoading={isLoading}>
Join & Konfirmasi
</ButtonCustom>
</TamplateBox>
</>
);
};
// 🟡 ZONA ACARA BERLANGSUN
// User sudah terdaftar & Event sedang berlangsung & user harus konfirmasi
const UserParticipan_And_DuringEvent = ({
id,
userId,
data,
}: {
id: string;
userId: string;
data: DataEvent;
}) => {
const [isLoading, setIsLoading] = useState<boolean>(false);
const handlerSubmit = async () => {
try {
setIsLoading(true);
const response = await apiEventConfirmationAction({
id: id as string,
userId: userId as string,
category: "confirmation",
});
if (!response.success) {
Toast.show({
type: "error",
text1: "Anda gagal konfirmasi",
});
return;
}
Toast.show({
type: "success",
text1: "Anda berhasil konfirmasi",
});
router.navigate(`/(application)/(user)/event/${id}/publish`);
} catch (error) {
console.log("[ERROR JOIN & CONFIRMATION EVENT]", error);
} finally {
setIsLoading(false);
}
};
return (
<>
<TamplateBox data={data}>
<TamplateText text="Anda sudah terdaftar sebagai peserta & Event sedang berlangsung. Silahkan konfirmasi kehadiran" />
<ButtonCustom onPress={() => handlerSubmit()} isLoading={isLoading}>
Konfirmasi
</ButtonCustom>
</TamplateBox>
</>
);
};

View File

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

View File

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

View File

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

View File

@@ -1,23 +1,14 @@
import {
BaseBox,
FloatingButton,
Grid,
LoaderCustom,
ProgressCustom,
StackCustom,
TextCustom,
ViewWrapper,
ViewWrapper
} from "@/components";
import API_STRORAGE from "@/constants/base-url-api-strorage";
import DUMMY_IMAGE from "@/constants/dummy-image-value";
import NoDataText from "@/components/_ShareComponent/NoDataText";
import Investment_BoxBerandaSection from "@/screens/Invesment/BoxBerandaSection";
import { apiInvestmentGetAll } from "@/service/api-client/api-investment";
import { Ionicons } from "@expo/vector-icons";
import dayjs from "dayjs";
import { Image } from "expo-image";
import { router, useFocusEffect } from "expo-router";
import _ from "lodash";
import { useCallback, useState } from "react";
import { View } from "react-native";
export default function InvestmentBursa() {
const [list, setList] = useState<any[] | null>(null);
@@ -32,8 +23,10 @@ export default function InvestmentBursa() {
const onLoadList = async () => {
try {
setLoadingList(true);
const response = await apiInvestmentGetAll();
console.log("[DATA LIST]", JSON.stringify(response.data, null, 2));
const response = await apiInvestmentGetAll({
category: "bursa"
});
// console.log("[DATA LIST]", JSON.stringify(response.data, null, 2));
setList(response.data);
} catch (error) {
console.log("[ERROR]", error);
@@ -52,95 +45,12 @@ export default function InvestmentBursa() {
{loadingList ? (
<LoaderCustom />
) : _.isEmpty(list) ? (
<TextCustom>Belum ada data</TextCustom>
<NoDataText />
) : (
list?.map((item: any, index: number) => (
<BaseBox
key={index}
paddingTop={7}
paddingBottom={7}
href={`/investment/${item.id}`}
>
<Grid>
<Grid.Col span={5}>
<Image
source={
item && item.imageId
? API_STRORAGE.GET({ fileId: item.imageId })
: DUMMY_IMAGE.background
}
style={{ width: "auto", height: 100, borderRadius: 10 }}
/>
</Grid.Col>
<Grid.Col span={1}>
<View />
</Grid.Col>
<Grid.Col span={6}>
<StackCustom>
<TextCustom truncate={2}>{item.title}</TextCustom>
<ProgressCustom
label={`${item.progress}%`}
value={item.progress}
size="lg"
/>
{Number(item?.pencarianInvestor) -
dayjs().diff(dayjs(item.countDown), "days") <=
0 ? (
<View
style={{
flexDirection: "row",
alignItems: "center",
gap: 5,
}}
>
<Ionicons
name="alert-circle-outline"
size={16}
color="red"
/>
<TextCustom color="red" size="small">
Periode Investasi Selesai
</TextCustom>
</View>
) : (
<TextCustom>
Sisa waktu:{" "}
{Number(item?.pencarianInvestor) -
dayjs().diff(dayjs(item.countDown), "days")}{" "}
hari
</TextCustom>
)}
</StackCustom>
</Grid.Col>
</Grid>
</BaseBox>
<Investment_BoxBerandaSection id={item.id} data={item} key={index} />
))
)}
</ViewWrapper>
);
}
// <View style={{ padding: 20, gap: 16 }}>
// <TextCustom>Progress 70%</TextCustom>
// <ProgressCustom value={70} color="primary" size="md" />
// <TextCustom>Success Progress</TextCustom>
// <ProgressCustom value={40} color="success" size="lg" />
// <TextCustom>Warning Progress (small)</TextCustom>
// <ProgressCustom value={90} color="warning" size="sm" />
// <TextCustom>Error Indeterminate</TextCustom>
// <ProgressCustom value={null} color="error" size="md" />
// <TextCustom>Custom Radius</TextCustom>
// <ProgressCustom value={60} color="info" size="xl" radius={4} />
// <ProgressCustom value={70} color="primary" size="lg" />
// <ProgressCustom value={45} color="success" size="md" label="Halfway!" />
// <ProgressCustom value={90} color="warning" size="lg" showLabel={false} />
// <ProgressCustom value={null} color="error" size="sm" label="Loading..." />
// </View>;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,21 +1,54 @@
/* eslint-disable react-hooks/exhaustive-deps */
import {
ActionIcon,
CenterCustom,
Divider,
StackCustom,
TextCustom,
ViewWrapper
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, useLocalSearchParams } from "expo-router";
import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
import _ from "lodash";
import React, { useCallback } from "react";
import { View } from "react-native";
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
@@ -45,36 +78,47 @@ export default function AdminDonasiListOfDisbursementOfFunds() {
/>
<Divider />
<StackCustom>
{Array.from({ length: 10 }).map((_, 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/${id}/detail-disbursement-of-funds`
);
}}
/>
</CenterCustom>
}
component2={
<TextCustom bold align="center" truncate>
{dayjs()
.add(index + 1, "day")
.format("DD-MM-YYYY HH:mm")}
</TextCustom>
}
component3={<TextCustom>Rp. 1.000.000</TextCustom>}
/>
<Divider />
</View>
))}
{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>
</>

View File

@@ -1,7 +1,9 @@
/* eslint-disable react-hooks/exhaustive-deps */
import {
ActionIcon,
BadgeCustom,
CenterCustom,
LoaderCustom,
SelectCustom,
StackCustom,
TextCustom,
@@ -10,23 +12,91 @@ import {
import { IconView } from "@/components/_Icon/IconComponent";
import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButtonAntTitle";
import { GridViewCustomSpan } from "@/components/_ShareComponent/GridViewCustomSpan";
import { MainColor } from "@/constants/color-palet";
import { ICON_SIZE_BUTTON } from "@/constants/constans-value";
import { dummyMasterStatusTransaction } from "@/lib/dummy-data/_master/status-transaction";
import { router, useLocalSearchParams } from "expo-router";
import React from "react";
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";
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={dummyMasterStatusTransaction}
onChange={(value) => console.log(value)}
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>
);
@@ -37,63 +107,78 @@ export default function AdminDonasiListOfDonatur() {
<AdminBackButtonAntTitle newComponent={searchComponent} />
}
>
<GridViewCustomSpan
span1={3}
span2={5}
span3={4}
component1={
<TextCustom bold align="center">
Aksi
</TextCustom>
}
component2={
<TextCustom bold align="center">
Donatur
</TextCustom>
}
component3={
<TextCustom bold align="center">
Status
</TextCustom>
}
/>
<Divider />
<StackCustom>
{Array.from({ length: 10 }).map((_, 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/${id}/berhasil/transaction-detail`
);
}}
/>
</CenterCustom>
}
component2={
<TextCustom bold align="center" truncate>
Bagas Banuna
</TextCustom>
}
component3={
<BadgeCustom
style={{ alignSelf: "center" }}
color={MainColor.green}
>
Berhasil
</BadgeCustom>
}
/>
<Divider />
</View>
))}
<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>
</>

View File

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

View File

@@ -1,69 +1,116 @@
/* eslint-disable react-hooks/exhaustive-deps */
import {
ActionIcon,
SearchInput,
Spacing,
TextCustom,
ViewWrapper
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, useLocalSearchParams } from "expo-router";
import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
import _ from "lodash";
import { useCallback, useState } from "react";
import { Divider } from "react-native-paper";
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={
<ViewWrapper headerComponent={<AdminTitlePage title="Donasi" />}>
<StackCustom gap={"sm"}>
<AdminComp_BoxTitle
title={`Donasi ${_.startCase(status as string)}`}
title={`${_.startCase(status as string)}`}
rightComponent={rightComponent}
/>
}
>
<AdminTitleTable
title1="Aksi"
title2="Username"
title3="Judul Donasi"
/>
<Spacing />
<Divider />
{Array.from({ length: 10 }).map((_, index) => (
<AdminTableValue
key={index}
value1={
<ActionIcon
icon={
<Octicons name="eye" size={ICON_SIZE_BUTTON} color="black" />
}
onPress={() => {
router.push(`/admin/donation/${index}/${status}`);
}}
/>
}
value2={<TextCustom truncate={1}>Username username</TextCustom>}
value3={
<TextCustom truncate={2}>
Lorem ipsum dolor sit amet consectetur adipisicing elit.
Blanditiis asperiores quidem deleniti architecto eaque et
nostrum, ad consequuntur eveniet quisquam quae voluptatum
ducimus! Dolorem nobis modi officia debitis, beatae mollitia.
</TextCustom>
}
<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>
</>
);

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,27 +1,67 @@
/* eslint-disable react-hooks/exhaustive-deps */
import {
ActionIcon,
BaseBox,
LoaderCustom,
SearchInput,
Spacing,
StackCustom,
TextCustom,
ViewWrapper,
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 { Octicons } from "@expo/vector-icons";
import { router, useLocalSearchParams } from "expo-router";
import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
import _ from "lodash";
import { useCallback, useState } from "react";
import { Divider } from "react-native-paper";
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 (
@@ -32,44 +72,50 @@ export default function AdminEventStatus() {
rightComponent={rightComponent}
/>
<BaseBox>
<StackCustom gap={"sm"}>
<AdminTitleTable
title1="Aksi"
title2="Username"
title3="Judul Event"
/>
<Spacing />
<Divider />
{Array.from({ length: 10 }).map((_, index) => (
<AdminTableValue
key={index}
value1={
<ActionIcon
icon={
<Octicons
name="eye"
size={ICON_SIZE_BUTTON}
color="black"
/>
}
onPress={() => {
router.push(`/admin/event/${index}/${status}`);
}}
/>
}
value2={<TextCustom truncate={1}>Username username</TextCustom>}
value3={
<TextCustom truncate={2}>
Lorem ipsum dolor sit amet consectetur adipisicing elit.
Blanditiis asperiores quidem deleniti architecto eaque et
nostrum, ad consequuntur eveniet quisquam quae voluptatum
ducimus! Dolorem nobis modi officia debitis, beatae mollitia.
</TextCustom>
}
/>
))}
</BaseBox>
{loadData ? (
<LoaderCustom />
) : _.isEmpty(listData) ? (
<TextCustom align="center" size="small" color="gray">Belum ada data</TextCustom>
) : (
listData?.map((item, index) => (
<AdminTableValue
key={index}
value1={
<ActionIcon
icon={
<Octicons
name="eye"
size={ICON_SIZE_BUTTON}
color="black"
/>
}
onPress={() => {
router.push(`/admin/event/${item.id}/${status}`);
}}
/>
}
value2={
<TextCustom truncate={1}>
{item?.Author?.username || "-"}
</TextCustom>
}
value3={
<TextCustom align="center" truncate={2}>
{item?.title || "-"}
</TextCustom>
}
/>
))
)}
</StackCustom>
</ViewWrapper>
</>
);

View File

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

View File

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

View File

@@ -1,23 +1,53 @@
import {
ActionIcon,
BaseBox,
BadgeCustom,
CenterCustom,
LoaderCustom,
Spacing,
StackCustom,
TextCustom,
ViewWrapper,
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 { GridDetail_4_8 } from "@/components/_ShareComponent/GridDetail_4_8";
import { GridViewCustomSpan } from "@/components/_ShareComponent/GridViewCustomSpan";
import { ICON_SIZE_BUTTON } from "@/constants/constans-value";
import { router } from "expo-router";
import { apiAdminMasterTypeOfEvent } from "@/service/api-admin/api-master-admin";
import { colorActivationForBadge } from "@/utils/colorActivationForBadge";
import { router, useFocusEffect } from "expo-router";
import _ from "lodash";
import { useCallback, useState } from "react";
import { View } from "react-native";
import { Divider } from "react-native-paper";
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" />}>
@@ -32,73 +62,68 @@ export default function AdminEventTypeOfEvent() {
}
/>
<BaseBox>
<GridDetail_4_8
label={
<>
<GridViewCustomSpan
span1={2}
span2={5}
span3={5}
component1={
<TextCustom bold align="center">
Aksi
</TextCustom>
}
value={<TextCustom bold>Tipe Acara</TextCustom>}
component2={<TextCustom bold align="center">Status</TextCustom>}
component3={<TextCustom bold>Tipe Acara</TextCustom>}
/>
<Divider />
<Spacing />
<StackCustom>
{listData.map((item, index) => (
<View key={index}>
<GridDetail_4_8
label={
<CenterCustom>
<ActionIcon
icon={
<IconEdit size={ICON_SIZE_BUTTON} color="black" />
}
onPress={() => {
router.push(`/admin/event/type-update?id=${index}`);
}}
/>
</CenterCustom>
}
value={<TextCustom bold>{item.label}</TextCustom>}
/>
<Divider />
</View>
))}
{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>
</BaseBox>
</>
</ViewWrapper>
</>
);
}
const listData = [
{
label: "Seminar",
value: "seminar",
},
{
label: "Workshop",
value: "workshop",
},
{
label: "Konferensi",
value: "konferensi",
},
{
label: "Lomba",
value: "lomba",
},
{
label: "Pameran",
value: "pameran",
},
{
label: "Pesta",
value: "pesta",
},
{
label: "Pertandingan",
value: "pertandingan",
},
];

View File

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

View File

@@ -1,3 +1,4 @@
/* eslint-disable react-hooks/exhaustive-deps */
import {
ActionIcon,
AlertDefaultSystem,
@@ -11,7 +12,7 @@ import {
Spacing,
StackCustom,
TextCustom,
ViewWrapper
ViewWrapper,
} from "@/components";
import { IconProspectus } from "@/components/_Icon";
import { IconDot, IconList } from "@/components/_Icon/IconComponent";
@@ -19,75 +20,141 @@ import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButt
import AdminButtonReject from "@/components/_ShareComponent/Admin/ButtonReject";
import AdminButtonReview from "@/components/_ShareComponent/Admin/ButtonReview";
import { GridDetail_4_8 } from "@/components/_ShareComponent/GridDetail_4_8";
import ReportBox from "@/components/Box/ReportBox";
import { MainColor } from "@/constants/color-palet";
import { ICON_SIZE_BUTTON } from "@/constants/constans-value";
import { router, useLocalSearchParams } from "expo-router";
import {
apiAdminInvestasiUpdateByStatus,
apiAdminInvestmentDetailById,
} from "@/service/api-admin/api-admin-investment";
import { colorBadgeStatus } from "@/utils/colorBadge";
import { formatCurrencyDisplay } from "@/utils/formatCurrencyDisplay";
import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
import _ from "lodash";
import React from "react";
import Toast from "react-native-toast-message";
export default function AdminInvestmentDetail() {
const { id, status } = useLocalSearchParams();
const [openDrawer, setOpenDrawer] = React.useState(false);
const colorBadge = () => {
if (status === "publish") {
return MainColor.green;
} else if (status === "review") {
return MainColor.orange;
} else if (status === "reject") {
return MainColor.red;
} else {
return MainColor.placeholder;
const [data, setData] = React.useState<any | null>(null);
const [isLoading, setLoading] = React.useState(false);
useFocusEffect(
React.useCallback(() => {
onLoadData();
}, [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);
}
} catch (error) {
console.log(error);
}
};
const listData = [
{
label: "Username",
value: `Bagas Banuna ${id}`,
value: (data && data?.author?.username) || "-",
},
{
label: "Judul",
value: `Donasi Lorem ipsum dolor sit amet, consectetur adipisicing elit.`,
value: (data && data?.title) || "-",
},
{
label: "Status",
value: (
<BadgeCustom color={colorBadge()}>
{_.startCase(status as string)}
</BadgeCustom>
),
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 10.000.000",
value: `Rp. ${
(data && data?.targetDana && formatCurrencyDisplay(data?.targetDana)) ||
"-"
}`,
},
{
label: "Harga Perlembar",
value: "Rp 2500",
value: `Rp. ${
(data &&
data?.hargaLembar &&
formatCurrencyDisplay(data?.hargaLembar)) ||
"-"
}`,
},
{
label: "Total Lembar",
value: "2490 lembar",
value:
(data &&
data?.totalLembar &&
formatCurrencyDisplay(data?.totalLembar)) ||
"-",
},
{
label: "ROI",
value: "4 %",
value: `${(data && data?.roi && data?.roi) || 0} %`,
},
{
label: "Pembagian Deviden",
value: "3 bulan",
value: (data && data?.MasterPembagianDeviden?.name) + " bulan" || "-",
},
{
label: "Jadwal Pembagian",
value: "Selamanya",
value: (data && data?.MasterPeriodeDeviden?.name) || "-",
},
{
label: "Pencarian Investor",
value: "30 Hari",
value: (data && data?.MasterPencarianInvestor?.name) + " hari" || "-",
},
];
const handlerSubmitPublish = async () => {
try {
setLoading(true);
const response = await apiAdminInvestasiUpdateByStatus({
id: id as string,
status: "publish",
data: data,
});
// console.log("[GET ON INVEST]", JSON.stringify(response, null, 2));
if (!response.success) {
Toast.show({
type: "error",
text1: "Gagal mempublikasikan data",
});
return;
}
Toast.show({
type: "success",
text1: "Berhasil mempublikasikan data",
});
router.replace(`/admin/investment/publish/status`);
} catch (error) {
console.log("[ERROR]", error);
} finally {
setLoading(false);
}
};
const rightComponent = (
<ActionIcon
icon={<IconDot size={ICON_SIZE_BUTTON} />}
@@ -109,16 +176,28 @@ export default function AdminInvestmentDetail() {
>
{status === "publish" && (
<BaseBox>
<ProgressCustom size="lg" />
<ProgressCustom
label={data && `${data.progress}%` || "0%"}
value={data && data.progress || 0}
size="lg"
/>
<Spacing />
<StackCustom gap={"xs"}>
<GridDetail_4_8
label={<TextCustom bold>Sisa Saham</TextCustom>}
value={<TextCustom>2490 lembar</TextCustom>}
value={
<TextCustom>
{data && formatCurrencyDisplay(data && data?.sisaLembar)} lembar
</TextCustom>
}
/>
<GridDetail_4_8
label={<TextCustom bold>Validasi Transaksi</TextCustom>}
value={<TextCustom>4 Transaksi</TextCustom>}
value={
<TextCustom>
{data && data?.Investasi_Invoice.length} Proses
</TextCustom>
}
/>
</StackCustom>
</BaseBox>
@@ -126,7 +205,7 @@ export default function AdminInvestmentDetail() {
<BaseBox>
<StackCustom>
<DummyLandscapeImage />
<DummyLandscapeImage imageId={data?.imageId} />
{listData.map((item, i) => (
<GridDetail_4_8
key={i}
@@ -150,7 +229,9 @@ export default function AdminInvestmentDetail() {
/>
}
onPress={() => {
router.push(`/(application)/(file)/${id}`);
router.push(
`/(application)/(file)/${data?.prospektusFileId}`
);
}}
>
Preview
@@ -161,46 +242,66 @@ export default function AdminInvestmentDetail() {
label={<TextCustom bold>File Dokumen</TextCustom>}
value={
<StackCustom>
{Array.from({ length: 5 }).map((_, i) => (
<ButtonCustom
key={i}
iconLeft={
<IconProspectus
size={ICON_SIZE_BUTTON}
color={MainColor.darkblue}
/>
}
onPress={() => {
router.push(`/(application)/(file)/${id}`);
}}
>
Dokumen {i + 1}
</ButtonCustom>
))}
{_.isEmpty(data?.DokumenInvestasi) ? (
<TextCustom align="center">-</TextCustom>
) : (
data?.DokumenInvestasi?.map((item: any, index: number) => {
const titleFix = item?.title?.substring(0, 10) || "";
return (
<ButtonCustom
key={item.id || index} // ✅ pastikan key unik
iconLeft={
<IconProspectus
size={ICON_SIZE_BUTTON}
color={MainColor.darkblue}
/>
}
onPress={() => {
router.push(
`/(application)/(file)/${item?.fileId}`
);
}}
>
<TextCustom color="black" truncate>
{titleFix}...
</TextCustom>
</ButtonCustom>
);
})
)}
</StackCustom>
}
/>
</StackCustom>
</BaseBox>
{data &&
data?.catatan &&
(status === "review" || status === "reject") && (
<ReportBox text={data?.catatan} />
)}
{status === "review" && (
<AdminButtonReview
isLoading={isLoading}
onPublish={() => {
AlertDefaultSystem({
title: "Publish",
message: "Apakah anda yakin ingin mempublikasikan data ini?",
textLeft: "Batal",
textRight: "Ya",
onPressLeft: () => {
router.back();
},
onPressRight: () => {
router.back();
handlerSubmitPublish();
},
});
}}
onReject={() => {
router.push(`/admin/investment/${id}/reject-input`);
router.push(
`/admin/investment/${id}/reject-input?status=${_.lowerCase(
data?.MasterStatusInvestasi?.name
)}`
);
}}
/>
)}

View File

@@ -1,67 +1,226 @@
/* eslint-disable react-hooks/exhaustive-deps */
import {
AlertDefaultSystem,
BadgeCustom,
BaseBox,
BoxButtonOnFooter,
ButtonCustom,
Spacing,
StackCustom,
TextCustom,
ViewWrapper,
} from "@/components";
import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButtonAntTitle";
import { GridDetail_4_8 } from "@/components/_ShareComponent/GridDetail_4_8";
import GridTwoView from "@/components/_ShareComponent/GridTwoView";
import { MainColor } from "@/constants/color-palet";
import dayjs from "dayjs";
import { router, useLocalSearchParams } from "expo-router";
import {
apiAdminInvestmentGetOneInvoiceById,
apiAdminInvestmentUpdateInvoice,
} from "@/service/api-admin/api-admin-investment";
import { colorBadgeTransaction } from "@/utils/colorBadge";
import { dateTimeView } from "@/utils/dateTimeView";
import { formatCurrencyDisplay } from "@/utils/formatCurrencyDisplay";
import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
import { useCallback, useState } from "react";
import Toast from "react-native-toast-message";
export default function AdminInvestmentTransactionDetail() {
const { id } = useLocalSearchParams();
const [data, setData] = useState<any | null>(null);
const [isLoading, setLoading] = useState<boolean>(false);
const buttonAction = (
<BoxButtonOnFooter>
<ButtonCustom onPress={() => router.back()}>Terima</ButtonCustom>
</BoxButtonOnFooter>
useFocusEffect(
useCallback(() => {
onLoadData();
}, [id])
);
const onLoadData = async () => {
try {
const response = await apiAdminInvestmentGetOneInvoiceById({
id: id as string,
});
// console.log("[RESPONSE]", JSON.stringify(response, null, 2));
if (response.success) {
setData(response.data);
}
} catch (error) {
console.log("[ERROR]", error);
}
};
const listData = [
{
label: "Investor",
value: "Bagas Banuna",
value: (data && data?.Author?.username) || "-",
},
{
label: "Bank",
value: "BCA",
value: (data && data?.MasterBank?.namaBank) || "-",
},
{
label: "Jumlah Investasi",
value: "Rp. 1.000.000",
value: (data && `Rp. ${formatCurrencyDisplay(data?.nominal)}`) || "-",
},
{
label: "Lembar terbeli",
value: (data && formatCurrencyDisplay(data?.lembarTerbeli)) || "-",
},
{
label: "Status",
value: <BadgeCustom color={MainColor.green}>Berhasil</BadgeCustom>,
value:
data && data?.StatusInvoice?.name ? (
<BadgeCustom
color={colorBadgeTransaction({
status: data?.StatusInvoice?.name,
})}
>
{data?.StatusInvoice?.name}
</BadgeCustom>
) : (
"-"
),
},
{
label: "Tanggal",
value: dayjs().format("DD-MM-YYYY HH:mm:ss"),
value: (data && dateTimeView({ date: data?.createdAt })) || "-",
},
{
label: "Bukti Transfer",
value: (
<ButtonCustom
onPress={() =>
router.push(`/(application)/(image)/preview-image/${id}`)
}
>
Cek
</ButtonCustom>
),
value:
data && data?.imageId ? (
<ButtonCustom
onPress={() =>
router.push(
`/(application)/(image)/preview-image/${data?.imageId}`
)
}
>
Cek
</ButtonCustom>
) : (
"-"
),
},
];
const handlerSubmit = async ({
category,
}: {
category: "accept" | "deny";
}) => {
try {
setLoading(true);
const response = await apiAdminInvestmentUpdateInvoice({
id: id as string,
category: category,
data: {
investasiId: data?.investasiId,
lembarTerbeli: data?.lembarTerbeli,
},
});
// console.log("[RESPONSE SUBMIT]", JSON.stringify(response, null, 2));
if (!response.success) {
Toast.show({
type: "error",
text1: "Gagal update status transaksi",
});
return;
}
Toast.show({
type: "success",
text1: "Berhasil update status transaksi",
});
router.back();
} catch (error) {
console.log("[ERROR]", error);
} finally {
setLoading(false);
}
};
const buttonAction = () => {
if (data?.StatusInvoice?.name === "Proses") {
return (
<GridTwoView
spanLeft={6}
spanRight={6}
styleLeft={{ paddingRight: 10 }}
styleRight={{ paddingLeft: 10 }}
leftIcon={
<ButtonCustom
isLoading={isLoading}
backgroundColor={MainColor.red}
textColor="white"
onPress={() => {
AlertDefaultSystem({
title: "Konfirmasi transaksi",
message: "Apakah anda yakin ingin menolak transaksi ini?",
textLeft: "Tidak",
textRight: "Ya",
onPressRight: () => {
handlerSubmit({
category: "deny",
});
},
});
}}
>
Tolak
</ButtonCustom>
}
rightIcon={
<ButtonCustom
isLoading={isLoading}
onPress={() => {
AlertDefaultSystem({
title: "Konfirmasi transaksi",
message: "Apakah anda yakin ingin menyetujui transaksi ini?",
textLeft: "Tidak",
textRight: "Ya",
onPressRight: () => {
handlerSubmit({
category: "accept",
});
},
});
}}
>
Terima
</ButtonCustom>
}
/>
);
} else if (data?.StatusInvoice?.name === "Gagal") {
return (
<>
<ButtonCustom textColor="red" onPress={() => router.back()}>
Gagal
</ButtonCustom>
</>
);
} else {
return (
<>
<ButtonCustom disabled={true}>
Status: {data?.StatusInvoice?.name}
</ButtonCustom>
</>
);
}
};
return (
<>
<ViewWrapper
headerComponent={<AdminBackButtonAntTitle title="Detail Transaksi Investor" />}
footerComponent={buttonAction}
headerComponent={
<AdminBackButtonAntTitle title="Detail Transaksi Investor" />
}
// footerComponent={buttonAction()}
>
<BaseBox>
<StackCustom>
@@ -74,6 +233,8 @@ export default function AdminInvestmentTransactionDetail() {
))}
</StackCustom>
</BaseBox>
<Spacing />
{buttonAction()}
</ViewWrapper>
</>
);

View File

@@ -1,7 +1,9 @@
/* eslint-disable react-hooks/exhaustive-deps */
import {
ActionIcon,
BadgeCustom,
CenterCustom,
LoaderCustom,
SelectCustom,
StackCustom,
TextCustom,
@@ -10,23 +12,98 @@ import {
import { IconView } from "@/components/_Icon/IconComponent";
import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButtonAntTitle";
import { GridViewCustomSpan } from "@/components/_ShareComponent/GridViewCustomSpan";
import { MainColor } from "@/constants/color-palet";
import NoDataText from "@/components/_ShareComponent/NoDataText";
import { ICON_SIZE_BUTTON } from "@/constants/constans-value";
import { dummyMasterStatusTransaction } from "@/lib/dummy-data/_master/status-transaction";
import { router, useLocalSearchParams } from "expo-router";
import React from "react";
import { apiAdminInvestmentListOfInvestor } from "@/service/api-admin/api-admin-investment";
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";
export default function AdminInvestmentListOfInvestor() {
const { id } = useLocalSearchParams();
console.log("[ID]", id);
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
);
useEffect(() => {
onLoadMaster();
}, []);
const onLoadMaster = async () => {
try {
const response = await apiMasterTransaction();
if (response.success) {
setMaster(response.data);
}
} catch (error) {
console.log("[ERROR]", error);
setMaster([]);
}
};
useFocusEffect(
React.useCallback(() => {
onLoadData();
}, [id, selectValue])
);
const onLoadData = async () => {
try {
setLoadData(true);
const response = await apiAdminInvestmentListOfInvestor({
id: id as string,
status: selectedStatus as any,
});
console.log("[LIST OF INVESTOR]", JSON.stringify(response, null, 2));
if (response.success) {
setListData(response.data);
}
} catch (error) {
console.log("[ERROR]", error);
setListData([]);
} finally {
setLoadData(false);
}
};
useEffect(() => {
onLoadMaster();
}, []);
const searchComponent = (
<View style={{ flexDirection: "row", gap: 5 }}>
<SelectCustom
placeholder="Pilih status transaksi"
data={dummyMasterStatusTransaction}
onChange={(value) => console.log(value)}
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>
);
@@ -40,66 +117,77 @@ export default function AdminInvestmentListOfInvestor() {
return (
<>
<ViewWrapper
headerComponent={headerComponent}
>
<GridViewCustomSpan
span1={3}
span2={5}
span3={4}
component1={
<TextCustom bold align="center">
Aksi
</TextCustom>
}
component2={
<TextCustom bold align="center">
Investor
</TextCustom>
}
component3={
<TextCustom bold align="center">
Status
</TextCustom>
}
/>
<Divider />
<ViewWrapper headerComponent={headerComponent}>
<StackCustom>
{Array.from({ length: 10 }).map((_, 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/investment/${id}/berhasil/transaction-detail`
);
}}
/>
</CenterCustom>
}
component2={
<TextCustom bold align="center" truncate>
Bagas Banuna
</TextCustom>
}
component3={
<BadgeCustom
style={{ alignSelf: "center" }}
color={MainColor.green}
>
Berhasil
</BadgeCustom>
}
/>
<Divider />
</View>
))}
<GridViewCustomSpan
span1={3}
span2={5}
span3={4}
component1={
<TextCustom bold align="center">
Aksi
</TextCustom>
}
component2={
<TextCustom bold align="center">
Investor
</TextCustom>
}
component3={
<TextCustom bold align="center">
Status
</TextCustom>
}
/>
<Divider />
<StackCustom>
{loadData ? (
<LoaderCustom />
) : _.isEmpty(listData) ? (
<NoDataText />
) : (
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/investment/${item?.id}/${_.lowerCase(
item?.StatusInvoice?.name
)}/transaction-detail`
);
}}
/>
</CenterCustom>
}
component2={
<TextCustom bold align="center" truncate>
{item?.Author?.username || "-"}
</TextCustom>
}
component3={
<BadgeCustom
style={{ alignSelf: "center" }}
color={colorBadgeTransaction({
status: item?.StatusInvoice?.name,
})}
>
{item?.StatusInvoice?.name}
</BadgeCustom>
}
/>
</View>
))
)}
</StackCustom>
</StackCustom>
</ViewWrapper>
</>

View File

@@ -1,3 +1,4 @@
/* eslint-disable react-hooks/exhaustive-deps */
import {
AlertDefaultSystem,
BoxButtonOnFooter,
@@ -6,15 +7,83 @@ import {
} from "@/components";
import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButtonAntTitle";
import AdminButtonReject from "@/components/_ShareComponent/Admin/ButtonReject";
import { router, useLocalSearchParams } from "expo-router";
import { useState } from "react";
import { apiAdminInvestasiUpdateByStatus, apiAdminInvestmentDetailById } from "@/service/api-admin/api-admin-investment";
import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
import { useCallback, useState } from "react";
import Toast from "react-native-toast-message";
export default function AdminInvestmentRejectInput() {
const { id } = useLocalSearchParams();
const [value, setValue] = useState(id as string);
const { id, status } = useLocalSearchParams();
console.log("[STATUS]", status);
const [value, setValue] = useState<any | null>(null);
const [isLoading , setLoading] = useState(false)
useFocusEffect(
useCallback(() => {
onLoadData();
}, [id])
);
const onLoadData = async () => {
try {
const response = await apiAdminInvestmentDetailById({ id: id as string });
console.log("[DATA]", JSON.stringify(response, null, 2));
if (response.success) {
setValue(response.data?.catatan);
}
} catch (error) {
console.log(error);
}
};
const handlerSubmit = async () => {
if (!value) {
Toast.show({
type: "error",
text1: "Harap masukan alasan penolakan",
});
return;
}
try {
setLoading(true)
const response = await apiAdminInvestasiUpdateByStatus({
id: id as string,
status: "reject",
data: value,
});
console.log("[RESPONSE]", JSON.stringify(response, null, 2));
if (!response.success) {
Toast.show({
type: "error",
text1: "Gagal melakukan report",
});
return;
}
Toast.show({
type: "success",
text1: "Berhasil melakukan report",
});
if (status === "review") {
router.replace(`/admin/investment/reject/status`);
} else {
router.back();
}
} catch (error) {
console.error(["ERROR"], error);
} finally {
setLoading(false)
}
};
const buttonSubmit = (
<BoxButtonOnFooter>
<AdminButtonReject
isLoading={isLoading}
title="Reject"
onReject={() =>
AlertDefaultSystem({
@@ -22,12 +91,8 @@ export default function AdminInvestmentRejectInput() {
message: "Apakah anda yakin ingin menolak data ini?",
textLeft: "Batal",
textRight: "Ya",
onPressLeft: () => {
router.back();
},
onPressRight: () => {
console.log("value:", value);
router.replace(`/admin/investment/reject/status`);
handlerSubmit();
},
})
}
@@ -39,7 +104,9 @@ export default function AdminInvestmentRejectInput() {
<>
<ViewWrapper
footerComponent={buttonSubmit}
headerComponent={<AdminBackButtonAntTitle title="Penolakan Investasi" />}
headerComponent={
<AdminBackButtonAntTitle title="Penolakan Investasi" />
}
>
<TextAreaCustom
value={value}

View File

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

View File

@@ -7,8 +7,51 @@ import {
import AdminComp_BoxDashboard from "@/components/_ShareComponent/Admin/BoxDashboard";
import AdminTitlePage from "@/components/_ShareComponent/Admin/TitlePage";
import { MainColor } from "@/constants/color-palet";
import { apiAdminInvestment } from "@/service/api-admin/api-admin-investment";
import { useFocusEffect } from "expo-router";
import React, { useCallback } from "react";
export default function AdminInvestment() {
const [data, setData] = React.useState<any | null>(null);
useFocusEffect(
useCallback(() => {
onLoadData();
}, [])
);
const onLoadData = async () => {
try {
const response = await apiAdminInvestment({
category: "dashboard",
});
console.log(JSON.stringify(response, null, 2));
if (response.success) {
setData(response.data);
}
} catch (error) {
console.log(error);
}
};
const listData = [
{
label: "Publish",
value: (data && data.publish) || 0,
icon: <IconPublish size={25} color={MainColor.green} />,
},
{
label: "Review",
value: (data && data.review) || 0,
icon: <IconReview size={25} color={MainColor.orange} />,
},
{
label: "Reject",
value: (data && data.reject) || 0,
icon: <IconReject size={25} color={MainColor.red} />,
},
];
return (
<>
<ViewWrapper>
@@ -23,21 +66,3 @@ export default function AdminInvestment() {
</>
);
}
const listData = [
{
label: "Publish",
value: 3,
icon: <IconPublish size={25} color={MainColor.green} />,
},
{
label: "Review",
value: 5,
icon: <IconReview size={25} color={MainColor.orange} />,
},
{
label: "Reject",
value: 8,
icon: <IconReject size={25} color={MainColor.red} />,
},
];

View File

@@ -13,8 +13,9 @@ import {
import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButtonAntTitle";
import AdminButtonReject from "@/components/_ShareComponent/Admin/ButtonReject";
import AdminButtonReview from "@/components/_ShareComponent/Admin/ButtonReview";
import ReportBox from "@/components/Box/ReportBox";
import { MainColor } from "@/constants/color-palet";
import funUpdateStatus from "@/screens/Admin/Job/funUpdateStatus";
import funUpdateStatusJob from "@/screens/Admin/Job/funUpdateStatus";
import { apiAdminJobGetById } from "@/service/api-admin/api-admin-job";
import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
import _ from "lodash";
@@ -88,7 +89,7 @@ export default function AdminJobDetailStatus() {
changeStatus: "publish" | "review" | "reject";
}) => {
try {
const response = await funUpdateStatus({
const response = await funUpdateStatusJob({
id: id as string,
changeStatus,
});
@@ -141,13 +142,8 @@ export default function AdminJobDetailStatus() {
</StackCustom>
</BaseBox>
{data && data?.catatan && (
<BaseBox>
<StackCustom>
<TextCustom bold>Catatan report</TextCustom>
<TextCustom>{data?.catatan}</TextCustom>
</StackCustom>
</BaseBox>
{data && data?.catatan && (status === "reject" || status === "review") && (
<ReportBox text={data?.catatan}/>
)}
{status === "review" && (

View File

@@ -7,7 +7,7 @@ import {
} from "@/components";
import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButtonAntTitle";
import AdminButtonReject from "@/components/_ShareComponent/Admin/ButtonReject";
import funUpdateStatus from "@/screens/Admin/Job/funUpdateStatus";
import funUpdateStatusJob from "@/screens/Admin/Job/funUpdateStatus";
import { apiAdminJobGetById } from "@/service/api-admin/api-admin-job";
import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
import { useCallback, useState } from "react";
@@ -45,7 +45,7 @@ export default function AdminJobRejectInput() {
}) => {
try {
setIsLoading(true);
const response = await funUpdateStatus({
const response = await funUpdateStatusJob({
id: id as string,
changeStatus,
data: data,

View File

@@ -1,21 +1,24 @@
/* eslint-disable react-hooks/exhaustive-deps */
import {
AlertDefaultSystem,
BadgeCustom,
BaseBox,
CircleContainer,
Grid,
Spacing,
StackCustom,
TextCustom,
ViewWrapper,
AlertDefaultSystem,
BadgeCustom,
BaseBox,
CircleContainer,
Grid,
Spacing,
StackCustom,
TextCustom,
ViewWrapper,
} from "@/components";
import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButtonAntTitle";
import AdminButtonReject from "@/components/_ShareComponent/Admin/ButtonReject";
import AdminButtonReview from "@/components/_ShareComponent/Admin/ButtonReview";
import { GridDetail_4_8 } from "@/components/_ShareComponent/GridDetail_4_8";
import ReportBox from "@/components/Box/ReportBox";
import { MainColor } from "@/constants/color-palet";
import funUpdateStatusVoting from "@/screens/Admin/Voting/funUpdateStatus";
import { apiAdminVotingById } from "@/service/api-admin/api-admin-voting";
import { colorBadgeStatus } from "@/utils/colorBadge";
import { dateTimeView } from "@/utils/dateTimeView";
import { Entypo } from "@expo/vector-icons";
import dayjs from "dayjs";
@@ -23,11 +26,14 @@ import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
import _ from "lodash";
import { useCallback, useState } from "react";
import { List } from "react-native-paper";
import Toast from "react-native-toast-message";
export default function AdminVotingDetail() {
const { id, status } = useLocalSearchParams();
const [data, setData] = useState<any | null>(null);
const [isLoading, setIsLoading] = useState(false);
console.log("[status]", status);
useFocusEffect(
useCallback(() => {
@@ -41,8 +47,6 @@ export default function AdminVotingDetail() {
id: id as string,
});
console.log("[DATA BY ID]", JSON.stringify(response, null, 2));
if (response.success) {
setData(response.data);
}
@@ -51,18 +55,6 @@ export default function AdminVotingDetail() {
}
};
const colorBadge = () => {
if (status === "publish") {
return MainColor.green;
} else if (status === "review") {
return MainColor.orange;
} else if (status === "reject") {
return MainColor.red;
} else {
return MainColor.placeholder;
}
};
const listData = [
{
label: "Username",
@@ -76,8 +68,8 @@ export default function AdminVotingDetail() {
label: "Status",
value:
data && data?.Voting_Status?.name ? (
<BadgeCustom color={colorBadge()}>
{_.startCase(data?.Voting_Status?.name)}
<BadgeCustom color={colorBadgeStatus({ status: status as string })}>
{status === "history" ? "Riwayat" : _.startCase(status as string)}
</BadgeCustom>
) : (
"-"
@@ -116,6 +108,59 @@ export default function AdminVotingDetail() {
},
];
const handleUpdate = async ({
changeStatus,
}: {
changeStatus: "publish" | "review" | "reject";
}) => {
try {
const dateNow = new Date();
// const dateNowHour = dateNow.getHours();
// const awalVoteHour = dayjs(data?.awalVote).hour();
const isBefore = dayjs(dateNow).diff(dayjs(data?.awalVote), "hours") < 0;
console.log("[IS BEFORE]", isBefore);
if (!isBefore) {
Toast.show({
type: "error",
text1: "Tanggal & waktu telah lewat",
text2: "Silahkan report dan ubah tanggal & waktu voting",
});
return;
}
Toast.show({
type: "success",
text1: "Berhasil mempublikasikan data",
});
setIsLoading(true);
const response = await funUpdateStatusVoting({
id: id as string,
changeStatus,
});
if (!response.success) {
Toast.show({
type: "error",
text1: "Gagal mempublikasikan data",
});
}
Toast.show({
type: "success",
text1: "Berhasil mempublikasikan data",
});
router.back();
} catch (error) {
console.log("[ERROR]", error);
} finally {
setIsLoading(false);
}
};
return (
<>
<ViewWrapper
@@ -133,52 +178,59 @@ export default function AdminVotingDetail() {
</StackCustom>
</BaseBox>
{status === "publish" && (
<BaseBox>
<TextCustom bold align="center">
Hasil Voting
</TextCustom>
<Spacing />
<Grid>
{Array.from({ length: 4 }).map((_, index) => (
<Grid.Col
key={index}
span={3}
style={{ paddingRight: 3, paddingLeft: 3 }}
>
<StackCustom gap={"sm"}>
<CircleContainer
value={(index % 3) * 3}
style={{ alignSelf: "center" }}
/>
<TextCustom size="small" align="center">
Pilihan {index + 1}
</TextCustom>
</StackCustom>
</Grid.Col>
))}
</Grid>
</BaseBox>
)}
{status === "publish" ||
(status === "history" && (
<BaseBox>
<TextCustom bold align="center">
Hasil Voting
</TextCustom>
<Spacing />
<Grid>
{data?.Voting_DaftarNamaVote?.map(
(item: any, index: number) => (
<Grid.Col
key={index}
span={12 / data?.Voting_DaftarNamaVote?.length}
style={{ paddingRight: 3, paddingLeft: 3 }}
>
<StackCustom gap={"sm"}>
<CircleContainer
value={item?.jumlah}
style={{ alignSelf: "center" }}
/>
<TextCustom size="small" align="center">
{item?.value}
</TextCustom>
</StackCustom>
</Grid.Col>
)
)}
</Grid>
</BaseBox>
))}
{data &&
data?.catatan &&
(status === "review" || status === "reject") && (
<ReportBox text={data?.catatan} />
)}
{status === "review" && (
<AdminButtonReview
isLoading={isLoading}
onPublish={() => {
AlertDefaultSystem({
title: "Publish",
message: "Apakah anda yakin ingin mempublikasikan data ini?",
textLeft: "Cancel",
textRight: "Publish",
onPressLeft: () => {
router.back();
},
onPressRight: () => {
router.back();
handleUpdate({ changeStatus: "publish" });
},
});
}}
onReject={() => {
router.push(`/admin/voting/${id}/reject-input`);
router.push(`/admin/voting/${id}/${status}/reject-input`);
}}
/>
)}
@@ -187,7 +239,7 @@ export default function AdminVotingDetail() {
<AdminButtonReject
title="Tambah Catatan"
onReject={() => {
router.push(`/admin/voting/${id}/reject-input`);
router.push(`/admin/voting/${id}/${status}/reject-input`);
}}
/>
)}

View File

@@ -0,0 +1,113 @@
/* eslint-disable react-hooks/exhaustive-deps */
import {
AlertDefaultSystem,
BoxButtonOnFooter,
TextAreaCustom,
ViewWrapper,
} from "@/components";
import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButtonAntTitle";
import AdminButtonReject from "@/components/_ShareComponent/Admin/ButtonReject";
import funUpdateStatusVoting from "@/screens/Admin/Voting/funUpdateStatus";
import { apiAdminVotingById } from "@/service/api-admin/api-admin-voting";
import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
import { useCallback, useState } from "react";
import Toast from "react-native-toast-message";
export default function AdminVotingRejectInput() {
const { id, status } = useLocalSearchParams();
const [data, setData] = useState("");
const [isLoading, setIsLoading] = useState(false);
useFocusEffect(
useCallback(() => {
onLoadData();
}, [id])
);
const onLoadData = async () => {
try {
const response = await apiAdminVotingById({
id: id as string,
});
if (response.success) {
setData(response.data.catatan);
}
} catch (error) {
console.log("[ERROR]", error);
}
};
const handleUpdate = async ({
changeStatus,
}: {
changeStatus: "publish" | "review" | "reject";
}) => {
try {
setIsLoading(true);
const response = await funUpdateStatusVoting({
id: id as string,
changeStatus,
data: data,
});
if (!response.success) {
Toast.show({
type: "error",
text1: "Report gagal",
});
}
Toast.show({
type: "success",
text1: "Report berhasil",
});
if (status === "review") {
router.replace(`/admin/voting/reject/status`);
} else if (status === "reject") {
router.back();
}
} catch (error) {
console.log("[ERROR]", error);
} finally {
setIsLoading(false);
}
};
const buttonSubmit = (
<BoxButtonOnFooter>
<AdminButtonReject
title="Reject"
isLoading={isLoading}
onReject={() =>
AlertDefaultSystem({
title: "Reject",
message: "Apakah anda yakin ingin menolak data ini?",
textLeft: "Batal",
textRight: "Ya",
onPressRight: () => handleUpdate({ changeStatus: "reject" }),
})
}
/>
</BoxButtonOnFooter>
);
return (
<>
<ViewWrapper
footerComponent={buttonSubmit}
headerComponent={<AdminBackButtonAntTitle title="Penolakan Voting" />}
>
<TextAreaCustom
value={data}
onChangeText={setData}
placeholder="Masukan alasan"
required
showCount
maxLength={1000}
/>
</ViewWrapper>
</>
);
}

View File

@@ -1,55 +0,0 @@
import {
AlertDefaultSystem,
BoxButtonOnFooter,
TextAreaCustom,
ViewWrapper,
} from "@/components";
import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButtonAntTitle";
import AdminButtonReject from "@/components/_ShareComponent/Admin/ButtonReject";
import { router, useLocalSearchParams } from "expo-router";
import { useState } from "react";
export default function AdminVotingRejectInput() {
const { id } = useLocalSearchParams();
const [value, setValue] = useState(id as string);
const buttonSubmit = (
<BoxButtonOnFooter>
<AdminButtonReject
title="Reject"
onReject={() =>
AlertDefaultSystem({
title: "Reject",
message: "Apakah anda yakin ingin menolak data ini?",
textLeft: "Batal",
textRight: "Ya",
onPressLeft: () => {
router.back();
},
onPressRight: () => {
console.log("value:", value);
router.replace(`/admin/voting/reject/status`);
},
})
}
/>
</BoxButtonOnFooter>
);
return (
<>
<ViewWrapper
footerComponent={buttonSubmit}
headerComponent={<AdminBackButtonAntTitle title="Penolakan Voting" />}
>
<TextAreaCustom
value={value}
onChangeText={setValue}
placeholder="Masukan alasan"
required
showCount
maxLength={1000}
/>
</ViewWrapper>
</>
);
}

View File

@@ -1,10 +1,8 @@
/* eslint-disable react-hooks/exhaustive-deps */
import {
ActionIcon,
BaseBox,
LoaderCustom,
SearchInput,
Spacing,
StackCustom,
TextCustom,
ViewWrapper,
@@ -18,13 +16,11 @@ import { apiAdminVoting } from "@/service/api-admin/api-admin-voting";
import { Octicons } from "@expo/vector-icons";
import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
import _ from "lodash";
import { useState, useCallback } from "react";
import { useCallback, useState } from "react";
import { Divider } from "react-native-paper";
export default function AdminVotingStatus() {
const { status } = useLocalSearchParams();
console.log("[STATUS]", status);
const [list, setList] = useState<any | null>(null);
const [loadList, setLoadList] = useState(false);
const [search, setSearch] = useState<string>("");
@@ -43,8 +39,6 @@ export default function AdminVotingStatus() {
search,
});
// console.log("[LIST BY STATUS]", JSON.stringify(response, null, 2));
if (response.success) {
setList(response.data);
}
@@ -79,31 +73,43 @@ export default function AdminVotingStatus() {
/>
<Divider />
{loadList ? <LoaderCustom/> : _.isEmpty(list) ? <TextCustom align="center" bold color="gray">Belum ada data</TextCustom> : list.map((item: any, i: number) => (
<AdminTableValue
key={i}
value1={
<ActionIcon
icon={
<Octicons
name="eye"
size={ICON_SIZE_BUTTON}
color="black"
/>
}
onPress={() => {
router.push(`/admin/voting/${item.id}/${status}`);
}}
/>
}
value2={<TextCustom truncate={1}>{item?.Author?.username || "-"}</TextCustom>}
value3={
<TextCustom align="center" truncate={2}>
{item?.title || "-"}
</TextCustom>
}
/>
))}
{loadList ? (
<LoaderCustom />
) : _.isEmpty(list) ? (
<TextCustom align="center" bold color="gray">
Belum ada data
</TextCustom>
) : (
list.map((item: any, i: number) => (
<AdminTableValue
key={i}
value1={
<ActionIcon
icon={
<Octicons
name="eye"
size={ICON_SIZE_BUTTON}
color="black"
/>
}
onPress={() => {
router.push(`/admin/voting/${item.id}/${status}`);
}}
/>
}
value2={
<TextCustom truncate={1}>
{item?.Author?.username || "-"}
</TextCustom>
}
value3={
<TextCustom align="center" truncate={2}>
{item?.title || "-"}
</TextCustom>
}
/>
))
)}
</StackCustom>
</ViewWrapper>
</>

View File

@@ -0,0 +1,104 @@
/* 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 { apiAdminVoting } from "@/service/api-admin/api-admin-voting";
import { Octicons } from "@expo/vector-icons";
import { router, useFocusEffect } from "expo-router";
import _ from "lodash";
import { useCallback, useState } from "react";
import { Divider } from "react-native-paper";
export default function AdminVotingHistory() {
const [list, setList] = useState<any | null>(null);
const [loadList, setLoadList] = useState(false);
const [search, setSearch] = useState<string>("");
useFocusEffect(
useCallback(() => {
onLoadData();
}, [ search])
);
const onLoadData = async () => {
try {
setLoadList(true);
const response = await apiAdminVoting({
category: "history",
search,
});
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"
value={search}
onChangeText={setSearch}
/>
);
return (
<>
<ViewWrapper headerComponent={<AdminTitlePage title="Voting" />}>
<AdminComp_BoxTitle
title="Riwayat"
rightComponent={rightComponent}
/>
<StackCustom gap={"sm"}>
<AdminTitleTable
title1="Aksi"
title2="Username"
title3="Judul Voting"
/>
<Divider />
{loadList ? <LoaderCustom/> : _.isEmpty(list) ? <TextCustom align="center" bold color="gray">Belum ada data</TextCustom> : list.map((item: any, i: number) => (
<AdminTableValue
key={i}
value1={
<ActionIcon
icon={
<Octicons
name="eye"
size={ICON_SIZE_BUTTON}
color="black"
/>
}
onPress={() => {
router.push(`/admin/voting/${item.id}/history`);
}}
/>
}
value2={<TextCustom truncate={1}>{item?.Author?.username || "-"}</TextCustom>}
value3={
<TextCustom align="center" truncate={2}>
{item?.title || "-"}
</TextCustom>
}
/>
))}
</StackCustom>
</ViewWrapper>
</>
);
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

After

Width:  |  Height:  |  Size: 509 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

After

Width:  |  Height:  |  Size: 509 KiB

View File

@@ -17,11 +17,11 @@
"axios": "^1.11.0",
"dayjs": "^1.11.13",
"expo": "^54.0.0",
"expo-blur": "~15.0.7",
"expo-camera": "~17.0.7",
"expo-clipboard": "~8.0.7",
"expo-constants": "~18.0.8",
"expo-constants": "^18.0.10",
"expo-dev-client": "~6.0.12",
"expo-device": "^8.0.9",
"expo-document-picker": "~14.0.7",
"expo-file-system": "^19.0.15",
"expo-font": "~14.0.8",
@@ -29,12 +29,13 @@
"expo-image": "~3.0.8",
"expo-image-picker": "~17.0.8",
"expo-linking": "~8.0.8",
"expo-notifications": "^0.32.13",
"expo-router": "~6.0.1",
"expo-splash-screen": "~31.0.9",
"expo-status-bar": "~3.0.8",
"expo-symbols": "~1.0.7",
"expo-system-ui": "~6.0.7",
"expo-web-browser": "~15.0.7",
"expo-web-browser": "~15.0.9",
"lodash": "^4.17.21",
"react": "19.1.0",
"react-dom": "19.1.0",
@@ -417,6 +418,8 @@
"@humanwhocodes/retry": ["@humanwhocodes/retry@0.4.3", "", {}, "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ=="],
"@ide/backoff": ["@ide/backoff@1.0.0", "", {}, "sha512-F0YfUDjvT+Mtt/R4xdl2X0EYCHMMiJqNLdxHD++jDT5ydEFIyqbCHh51Qx2E211dgZprPKhV7sHmnXKpLuvc5g=="],
"@isaacs/cliui": ["@isaacs/cliui@8.0.2", "", { "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", "strip-ansi": "^7.0.1", "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", "wrap-ansi": "^8.1.0", "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" } }, "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA=="],
"@isaacs/fs-minipass": ["@isaacs/fs-minipass@4.0.1", "", { "dependencies": { "minipass": "^7.0.4" } }, "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w=="],
@@ -801,6 +804,8 @@
"asap": ["asap@2.0.6", "", {}, "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA=="],
"assert": ["assert@2.1.0", "", { "dependencies": { "call-bind": "^1.0.2", "is-nan": "^1.3.2", "object-is": "^1.1.5", "object.assign": "^4.1.4", "util": "^0.12.5" } }, "sha512-eLHpSK/Y4nhMJ07gDaAzoX/XAKS8PSaojml3M0DM4JpV1LAi5JOJ/p6H/XWrl8L+DzVEvVCW1z3vWAaB9oTsQw=="],
"async-function": ["async-function@1.0.0", "", {}, "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA=="],
"async-limiter": ["async-limiter@1.0.1", "", {}, "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ=="],
@@ -839,6 +844,8 @@
"babel-preset-jest": ["babel-preset-jest@29.6.3", "", { "dependencies": { "babel-plugin-jest-hoist": "^29.6.3", "babel-preset-current-node-syntax": "^1.0.0" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA=="],
"badgin": ["badgin@1.2.3", "", {}, "sha512-NQGA7LcfCpSzIbGRbkgjgdWkjy7HI+Th5VLxTJfW5EeaAf3fnS+xWQaQOCYiny+q6QSvxqoSO04vCx+4u++EJw=="],
"balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
"base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="],
@@ -1151,15 +1158,15 @@
"expo": ["expo@54.0.2", "", { "dependencies": { "@babel/runtime": "^7.20.0", "@expo/cli": "54.0.2", "@expo/config": "~12.0.8", "@expo/config-plugins": "~54.0.1", "@expo/devtools": "0.1.7", "@expo/fingerprint": "0.15.0", "@expo/metro": "~0.1.1", "@expo/metro-config": "54.0.2", "@expo/vector-icons": "^15.0.2", "@ungap/structured-clone": "^1.3.0", "babel-preset-expo": "~54.0.0", "expo-asset": "~12.0.8", "expo-constants": "~18.0.8", "expo-file-system": "~19.0.12", "expo-font": "~14.0.8", "expo-keep-awake": "~15.0.7", "expo-modules-autolinking": "3.0.10", "expo-modules-core": "3.0.15", "pretty-format": "^29.7.0", "react-refresh": "^0.14.2", "whatwg-url-without-unicode": "8.0.0-3" }, "peerDependencies": { "@expo/dom-webview": "*", "@expo/metro-runtime": "*", "react": "*", "react-native": "*", "react-native-webview": "*" }, "optionalPeers": ["@expo/dom-webview", "@expo/metro-runtime", "react-native-webview"], "bin": { "expo": "bin/cli", "fingerprint": "bin/fingerprint", "expo-modules-autolinking": "bin/autolinking" } }, "sha512-0YcsvMuiZlVFVZRdD4PlhwsYrTmcN1qm5b1IRSLIeLZjH6ZnQwBb3KBSnK8WRC9V6EUPSbuLGpNT5AEewrtakQ=="],
"expo-asset": ["expo-asset@12.0.8", "", { "dependencies": { "@expo/image-utils": "^0.8.7", "expo-constants": "~18.0.8" }, "peerDependencies": { "expo": "*", "react": "*", "react-native": "*" } }, "sha512-jj2U8zw9+7orST2rlQGULYiqPoECOuUyffs2NguGrq84bYbkM041T7TOMXH2raPVJnM9lEAP54ezI6XL+GVYqw=="],
"expo-application": ["expo-application@7.0.7", "", { "peerDependencies": { "expo": "*" } }, "sha512-Jt1/qqnoDUbZ+bK91+dHaZ1vrPDtRBOltRa681EeedkisqguuEeUx4UHqwVyDK2oHWsK6lO3ojetoA4h8OmNcg=="],
"expo-blur": ["expo-blur@15.0.7", "", { "peerDependencies": { "expo": "*", "react": "*", "react-native": "*" } }, "sha512-SugQQbQd+zRPy8z2G5qDD4NqhcD7srBF7fN7O7yq6q7ZFK59VWvpDxtMoUkmSfdxgqONsrBN/rLdk00USADrMg=="],
"expo-asset": ["expo-asset@12.0.8", "", { "dependencies": { "@expo/image-utils": "^0.8.7", "expo-constants": "~18.0.8" }, "peerDependencies": { "expo": "*", "react": "*", "react-native": "*" } }, "sha512-jj2U8zw9+7orST2rlQGULYiqPoECOuUyffs2NguGrq84bYbkM041T7TOMXH2raPVJnM9lEAP54ezI6XL+GVYqw=="],
"expo-camera": ["expo-camera@17.0.7", "", { "dependencies": { "invariant": "^2.2.4" }, "peerDependencies": { "expo": "*", "react": "*", "react-native": "*", "react-native-web": "*" }, "optionalPeers": ["react-native-web"] }, "sha512-jdZfijfFjlVAuuIkDheA41YKpigPjqsN0juRvgyr7Lcyz+fvwZ3/RP50/n/hvuozH657wHxPfiyVVFa00z8TcQ=="],
"expo-clipboard": ["expo-clipboard@8.0.7", "", { "peerDependencies": { "expo": "*", "react": "*", "react-native": "*" } }, "sha512-zvlfFV+wB2QQrQnHWlo0EKHAkdi2tycLtE+EXFUWTPZYkgu1XcH+aiKfd4ul7Z0SDF+1IuwoiW9AA9eO35aj3Q=="],
"expo-constants": ["expo-constants@18.0.8", "", { "dependencies": { "@expo/config": "~12.0.8", "@expo/env": "~2.0.7" }, "peerDependencies": { "expo": "*", "react-native": "*" } }, "sha512-Tetphsx6RVImCTZeBAclRQMy0WOODY3y6qrUoc88YGUBVm8fAKkErCSWxLTCc6nFcJxdoOMYi62LgNIUFjZCLA=="],
"expo-constants": ["expo-constants@18.0.10", "", { "dependencies": { "@expo/config": "~12.0.10", "@expo/env": "~2.0.7" }, "peerDependencies": { "expo": "*", "react-native": "*" } }, "sha512-Rhtv+X974k0Cahmvx6p7ER5+pNhBC0XbP1lRviL2J1Xl4sT2FBaIuIxF/0I0CbhOsySf0ksqc5caFweAy9Ewiw=="],
"expo-dev-client": ["expo-dev-client@6.0.12", "", { "dependencies": { "expo-dev-launcher": "6.0.11", "expo-dev-menu": "7.0.11", "expo-dev-menu-interface": "2.0.0", "expo-manifests": "~1.0.8", "expo-updates-interface": "~2.0.0" }, "peerDependencies": { "expo": "*" } }, "sha512-Knr2abq0r6ALASsZtrX9QD4V0vP4ZL18iDVF5lgr6iFYawbuqQHuJRktIUETimu6qLusJK8Z3kZRabAdNqT+qw=="],
@@ -1169,6 +1176,8 @@
"expo-dev-menu-interface": ["expo-dev-menu-interface@2.0.0", "", { "peerDependencies": { "expo": "*" } }, "sha512-BvAMPt6x+vyXpThsyjjOYyjwfjREV4OOpQkZ0tNl+nGpsPfcY9mc6DRACoWnH9KpLzyIt3BOgh3cuy/h/OxQjw=="],
"expo-device": ["expo-device@8.0.9", "", { "dependencies": { "ua-parser-js": "^0.7.33" }, "peerDependencies": { "expo": "*" } }, "sha512-XqRpaljDNAYZGZzMpC+b9KZfzfydtkwx3pJAp6ODDH+O/5wjAw+mLc5wQMGJCx8/aqVmMsAokec7iebxDPFZDA=="],
"expo-document-picker": ["expo-document-picker@14.0.7", "", { "peerDependencies": { "expo": "*" } }, "sha512-81Jh8RDD0GYBUoSTmIBq30hXXjmkDV1ZY2BNIp1+3HR5PDSh2WmdhD/Ezz5YFsv46hIXHsQc+Kh1q8vn6OLT9Q=="],
"expo-file-system": ["expo-file-system@19.0.15", "", { "peerDependencies": { "expo": "*", "react-native": "*" } }, "sha512-sRLW+3PVJDiuoCE2LuteHhC7OxPjh1cfqLylf1YG1TDEbbQXnzwjfsKeRm6dslEPZLkMWfSLYIrVbnuq5mF7kQ=="],
@@ -1197,6 +1206,8 @@
"expo-modules-core": ["expo-modules-core@3.0.15", "", { "dependencies": { "invariant": "^2.2.4" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-vGI7osd0/IjprldD08k4bckWSu7ID4HhZNP68l/UtilONQ8XZig8mWJd/Fm7i7KGvE3HyuF+HOXE9l671no42Q=="],
"expo-notifications": ["expo-notifications@0.32.13", "", { "dependencies": { "@expo/image-utils": "^0.8.7", "@ide/backoff": "^1.0.0", "abort-controller": "^3.0.0", "assert": "^2.0.0", "badgin": "^1.1.5", "expo-application": "~7.0.7", "expo-constants": "~18.0.10" }, "peerDependencies": { "expo": "*", "react": "*", "react-native": "*" } }, "sha512-PL0R1ulLVUgAswlXtRDKxBlcipNM3YA6+P5nB5JIhXbsjLJ7y+EKVaEhHhbaGzuK1QVsRQSJNm/4oISX+vsmFQ=="],
"expo-router": ["expo-router@6.0.1", "", { "dependencies": { "@expo/metro-runtime": "6.1.1", "@expo/schema-utils": "^0.1.7", "@expo/server": "^0.7.4", "@radix-ui/react-slot": "1.2.0", "@radix-ui/react-tabs": "^1.1.12", "@react-navigation/bottom-tabs": "^7.4.0", "@react-navigation/native": "^7.1.8", "@react-navigation/native-stack": "^7.3.16", "client-only": "^0.0.1", "debug": "^4.3.4", "escape-string-regexp": "^4.0.0", "fast-deep-equal": "^3.1.3", "invariant": "^2.2.4", "nanoid": "^3.3.8", "query-string": "^7.1.3", "react-fast-compare": "^3.2.2", "react-native-is-edge-to-edge": "^1.1.6", "semver": "~7.6.3", "server-only": "^0.0.1", "sf-symbols-typescript": "^2.1.0", "shallowequal": "^1.1.0", "use-latest-callback": "^0.2.1", "vaul": "^1.1.2" }, "peerDependencies": { "@react-navigation/drawer": "^7.5.0", "@testing-library/react-native": ">= 12.0.0", "expo": "*", "expo-constants": "^18.0.8", "expo-linking": "^8.0.8", "react": "*", "react-dom": "*", "react-native": "*", "react-native-gesture-handler": "*", "react-native-reanimated": "*", "react-native-safe-area-context": ">= 5.4.0", "react-native-screens": "*", "react-native-web": "*", "react-server-dom-webpack": ">= 19.0.0" }, "optionalPeers": ["@react-navigation/drawer", "@testing-library/react-native", "react-dom", "react-native-gesture-handler", "react-native-reanimated", "react-native-web", "react-server-dom-webpack"] }, "sha512-5wXkWyNMqUbjCWH0PRkOM0P6UsgLVdgchDkiLz5FY7HfU00ToBcxij965bqtlaATBgoaIo4DuLu6EgxewrKJ8Q=="],
"expo-splash-screen": ["expo-splash-screen@31.0.9", "", { "dependencies": { "@expo/prebuild-config": "^54.0.1" }, "peerDependencies": { "expo": "*" } }, "sha512-pYLWFrEHn/c8e1a8oDzrlcyYI08oG4i37KA+VN5wwoC1DsQ0rm3kyAk0bmoydV1mBzNfXQFYbA1BGftXJl1f0w=="],
@@ -1209,7 +1220,7 @@
"expo-updates-interface": ["expo-updates-interface@2.0.0", "", { "peerDependencies": { "expo": "*" } }, "sha512-pTzAIufEZdVPKql6iMi5ylVSPqV1qbEopz9G6TSECQmnNde2nwq42PxdFBaUEd8IZJ/fdJLQnOT3m6+XJ5s7jg=="],
"expo-web-browser": ["expo-web-browser@15.0.7", "", { "peerDependencies": { "expo": "*", "react-native": "*" } }, "sha512-eXnfO3FQ2WthTA8uEPNJ7SDRfPaLIU/P2k082HGEYIHAFZMwh2o9Wo+SDVytO3E95TAv1qwhggUjOrczYzxteQ=="],
"expo-web-browser": ["expo-web-browser@15.0.9", "", { "peerDependencies": { "expo": "*", "react-native": "*" } }, "sha512-Dj8kNFO+oXsxqCDNlUT/GhOrJnm10kAElH++3RplLydogFm5jTzXYWDEeNIDmV+F+BzGYs+sIhxiBf7RyaxXZg=="],
"exponential-backoff": ["exponential-backoff@3.1.2", "", {}, "sha512-8QxYTVXUkuy7fIIoitQkPwGonB8F3Zj8eEO8Sqg9Zv/bkI7RJAzowee4gr81Hak/dUTpA2Z7VfQgoijjPNlUZA=="],
@@ -1381,6 +1392,8 @@
"invariant": ["invariant@2.2.4", "", { "dependencies": { "loose-envify": "^1.0.0" } }, "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA=="],
"is-arguments": ["is-arguments@1.2.0", "", { "dependencies": { "call-bound": "^1.0.2", "has-tostringtag": "^1.0.2" } }, "sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA=="],
"is-array-buffer": ["is-array-buffer@3.0.5", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "get-intrinsic": "^1.2.6" } }, "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A=="],
"is-arrayish": ["is-arrayish@0.3.2", "", {}, "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ=="],
@@ -1421,6 +1434,8 @@
"is-map": ["is-map@2.0.3", "", {}, "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw=="],
"is-nan": ["is-nan@1.3.2", "", { "dependencies": { "call-bind": "^1.0.0", "define-properties": "^1.1.3" } }, "sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w=="],
"is-negative-zero": ["is-negative-zero@2.0.3", "", {}, "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw=="],
"is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="],
@@ -1727,6 +1742,8 @@
"object-inspect": ["object-inspect@1.13.4", "", {}, "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="],
"object-is": ["object-is@1.1.6", "", { "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1" } }, "sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q=="],
"object-keys": ["object-keys@1.1.1", "", {}, "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA=="],
"object.assign": ["object.assign@4.1.7", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0", "has-symbols": "^1.1.0", "object-keys": "^1.1.1" } }, "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw=="],
@@ -2183,7 +2200,7 @@
"typescript": ["typescript@5.9.2", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A=="],
"ua-parser-js": ["ua-parser-js@1.0.40", "", { "bin": "script/cli.js" }, "sha512-z6PJ8Lml+v3ichVojCiB8toQJBuwR42ySM4ezjXIqXK3M0HczmKQ3LF4rhU55PfD99KEEXQG6yb7iOMyvYuHew=="],
"ua-parser-js": ["ua-parser-js@0.7.41", "", { "bin": { "ua-parser-js": "script/cli.js" } }, "sha512-O3oYyCMPYgNNHuO7Jjk3uacJWZF8loBgwrfd/5LE/HyZ3lUIOdniQ7DNXJcIgZbwioZxk0fLfI4EVnetdiX5jg=="],
"unbox-primitive": ["unbox-primitive@1.1.0", "", { "dependencies": { "call-bound": "^1.0.3", "has-bigints": "^1.0.2", "has-symbols": "^1.1.0", "which-boxed-primitive": "^1.1.1" } }, "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw=="],
@@ -2221,6 +2238,8 @@
"use-sync-external-store": ["use-sync-external-store@1.5.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A=="],
"util": ["util@0.12.5", "", { "dependencies": { "inherits": "^2.0.3", "is-arguments": "^1.0.4", "is-generator-function": "^1.0.7", "is-typed-array": "^1.1.3", "which-typed-array": "^1.1.2" } }, "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA=="],
"utils-merge": ["utils-merge@1.0.1", "", {}, "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA=="],
"uuid": ["uuid@7.0.3", "", { "bin": "dist/bin/uuid" }, "sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg=="],
@@ -2567,8 +2586,16 @@
"expo/babel-preset-expo": ["babel-preset-expo@54.0.0", "", { "dependencies": { "@babel/helper-module-imports": "^7.25.9", "@babel/plugin-proposal-decorators": "^7.12.9", "@babel/plugin-proposal-export-default-from": "^7.24.7", "@babel/plugin-syntax-export-default-from": "^7.24.7", "@babel/plugin-transform-class-static-block": "^7.27.1", "@babel/plugin-transform-export-namespace-from": "^7.25.9", "@babel/plugin-transform-flow-strip-types": "^7.25.2", "@babel/plugin-transform-modules-commonjs": "^7.24.8", "@babel/plugin-transform-object-rest-spread": "^7.24.7", "@babel/plugin-transform-parameters": "^7.24.7", "@babel/plugin-transform-private-methods": "^7.24.7", "@babel/plugin-transform-private-property-in-object": "^7.24.7", "@babel/plugin-transform-runtime": "^7.24.7", "@babel/preset-react": "^7.22.15", "@babel/preset-typescript": "^7.23.0", "@react-native/babel-preset": "0.81.4", "babel-plugin-react-compiler": "^19.1.0-rc.2", "babel-plugin-react-native-web": "~0.21.0", "babel-plugin-syntax-hermes-parser": "^0.25.1", "babel-plugin-transform-flow-enums": "^0.0.2", "debug": "^4.3.4", "resolve-from": "^5.0.0" }, "peerDependencies": { "@babel/runtime": "^7.20.0", "expo": "*", "react-refresh": ">=0.14.0 <1.0.0" }, "optionalPeers": ["@babel/runtime", "expo"] }, "sha512-a0Ej4ik6xzvtrA4Ivblov3XVvfntIoqnXOy2jG2k/3hzWqzrJxKyY2gUW9ZCMAicGevj2ju28q+TsK29uTe0eQ=="],
"expo/expo-constants": ["expo-constants@18.0.8", "", { "dependencies": { "@expo/config": "~12.0.8", "@expo/env": "~2.0.7" }, "peerDependencies": { "expo": "*", "react-native": "*" } }, "sha512-Tetphsx6RVImCTZeBAclRQMy0WOODY3y6qrUoc88YGUBVm8fAKkErCSWxLTCc6nFcJxdoOMYi62LgNIUFjZCLA=="],
"expo/expo-file-system": ["expo-file-system@19.0.12", "", { "peerDependencies": { "expo": "*", "react-native": "*" } }, "sha512-gqpxpnjfhzXLcqMOi49isB5S1Af49P9410fsaFfnLZWN3X6Dwc8EplDwbaolOI/wnGwP81P+/nDn5RNmU6m7mQ=="],
"expo-asset/expo-constants": ["expo-constants@18.0.8", "", { "dependencies": { "@expo/config": "~12.0.8", "@expo/env": "~2.0.7" }, "peerDependencies": { "expo": "*", "react-native": "*" } }, "sha512-Tetphsx6RVImCTZeBAclRQMy0WOODY3y6qrUoc88YGUBVm8fAKkErCSWxLTCc6nFcJxdoOMYi62LgNIUFjZCLA=="],
"expo-constants/@expo/config": ["@expo/config@12.0.10", "", { "dependencies": { "@babel/code-frame": "~7.10.4", "@expo/config-plugins": "~54.0.2", "@expo/config-types": "^54.0.8", "@expo/json-file": "^10.0.7", "deepmerge": "^4.3.1", "getenv": "^2.0.0", "glob": "^10.4.2", "require-from-string": "^2.0.2", "resolve-from": "^5.0.0", "resolve-workspace-root": "^2.0.0", "semver": "^7.6.0", "slugify": "^1.3.4", "sucrase": "3.35.0" } }, "sha512-lJMof5Nqakq1DxGYlghYB/ogSBjmv4Fxn1ovyDmcjlRsQdFCXgu06gEUogkhPtc9wBt9WlTTfqENln5HHyLW6w=="],
"expo-linking/expo-constants": ["expo-constants@18.0.8", "", { "dependencies": { "@expo/config": "~12.0.8", "@expo/env": "~2.0.7" }, "peerDependencies": { "expo": "*", "react-native": "*" } }, "sha512-Tetphsx6RVImCTZeBAclRQMy0WOODY3y6qrUoc88YGUBVm8fAKkErCSWxLTCc6nFcJxdoOMYi62LgNIUFjZCLA=="],
"expo-module-scripts/typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="],
"expo-modules-autolinking/commander": ["commander@7.2.0", "", {}, "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw=="],
@@ -2581,6 +2608,8 @@
"fbjs/promise": ["promise@7.3.1", "", { "dependencies": { "asap": "~2.0.3" } }, "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg=="],
"fbjs/ua-parser-js": ["ua-parser-js@1.0.40", "", { "bin": "script/cli.js" }, "sha512-z6PJ8Lml+v3ichVojCiB8toQJBuwR42ySM4ezjXIqXK3M0HczmKQ3LF4rhU55PfD99KEEXQG6yb7iOMyvYuHew=="],
"finalhandler/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="],
"finalhandler/encodeurl": ["encodeurl@1.0.2", "", {}, "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w=="],
@@ -2949,6 +2978,14 @@
"expect/jest-matcher-utils/jest-diff": ["jest-diff@29.7.0", "", { "dependencies": { "chalk": "^4.0.0", "diff-sequences": "^29.6.3", "jest-get-type": "^29.6.3", "pretty-format": "^29.7.0" } }, "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw=="],
"expo-constants/@expo/config/@babel/code-frame": ["@babel/code-frame@7.10.4", "", { "dependencies": { "@babel/highlight": "^7.10.4" } }, "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg=="],
"expo-constants/@expo/config/@expo/config-plugins": ["@expo/config-plugins@54.0.2", "", { "dependencies": { "@expo/config-types": "^54.0.8", "@expo/json-file": "~10.0.7", "@expo/plist": "^0.4.7", "@expo/sdk-runtime-versions": "^1.0.0", "chalk": "^4.1.2", "debug": "^4.3.5", "getenv": "^2.0.0", "glob": "^10.4.2", "resolve-from": "^5.0.0", "semver": "^7.5.4", "slash": "^3.0.0", "slugify": "^1.6.6", "xcode": "^3.0.1", "xml2js": "0.6.0" } }, "sha512-jD4qxFcURQUVsUFGMcbo63a/AnviK8WUGard+yrdQE3ZrB/aurn68SlApjirQQLEizhjI5Ar2ufqflOBlNpyPg=="],
"expo-constants/@expo/config/@expo/json-file": ["@expo/json-file@10.0.7", "", { "dependencies": { "@babel/code-frame": "~7.10.4", "json5": "^2.2.3" } }, "sha512-z2OTC0XNO6riZu98EjdNHC05l51ySeTto6GP7oSQrCvQgG9ARBwD1YvMQaVZ9wU7p/4LzSf1O7tckL3B45fPpw=="],
"expo-constants/@expo/config/semver": ["semver@7.7.2", "", { "bin": "bin/semver.js" }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="],
"expo/babel-preset-expo/@react-native/babel-preset": ["@react-native/babel-preset@0.81.4", "", { "dependencies": { "@babel/core": "^7.25.2", "@babel/plugin-proposal-export-default-from": "^7.24.7", "@babel/plugin-syntax-dynamic-import": "^7.8.3", "@babel/plugin-syntax-export-default-from": "^7.24.7", "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", "@babel/plugin-syntax-optional-chaining": "^7.8.3", "@babel/plugin-transform-arrow-functions": "^7.24.7", "@babel/plugin-transform-async-generator-functions": "^7.25.4", "@babel/plugin-transform-async-to-generator": "^7.24.7", "@babel/plugin-transform-block-scoping": "^7.25.0", "@babel/plugin-transform-class-properties": "^7.25.4", "@babel/plugin-transform-classes": "^7.25.4", "@babel/plugin-transform-computed-properties": "^7.24.7", "@babel/plugin-transform-destructuring": "^7.24.8", "@babel/plugin-transform-flow-strip-types": "^7.25.2", "@babel/plugin-transform-for-of": "^7.24.7", "@babel/plugin-transform-function-name": "^7.25.1", "@babel/plugin-transform-literals": "^7.25.2", "@babel/plugin-transform-logical-assignment-operators": "^7.24.7", "@babel/plugin-transform-modules-commonjs": "^7.24.8", "@babel/plugin-transform-named-capturing-groups-regex": "^7.24.7", "@babel/plugin-transform-nullish-coalescing-operator": "^7.24.7", "@babel/plugin-transform-numeric-separator": "^7.24.7", "@babel/plugin-transform-object-rest-spread": "^7.24.7", "@babel/plugin-transform-optional-catch-binding": "^7.24.7", "@babel/plugin-transform-optional-chaining": "^7.24.8", "@babel/plugin-transform-parameters": "^7.24.7", "@babel/plugin-transform-private-methods": "^7.24.7", "@babel/plugin-transform-private-property-in-object": "^7.24.7", "@babel/plugin-transform-react-display-name": "^7.24.7", "@babel/plugin-transform-react-jsx": "^7.25.2", "@babel/plugin-transform-react-jsx-self": "^7.24.7", "@babel/plugin-transform-react-jsx-source": "^7.24.7", "@babel/plugin-transform-regenerator": "^7.24.7", "@babel/plugin-transform-runtime": "^7.24.7", "@babel/plugin-transform-shorthand-properties": "^7.24.7", "@babel/plugin-transform-spread": "^7.24.7", "@babel/plugin-transform-sticky-regex": "^7.24.7", "@babel/plugin-transform-typescript": "^7.25.2", "@babel/plugin-transform-unicode-regex": "^7.24.7", "@babel/template": "^7.25.0", "@react-native/babel-plugin-codegen": "0.81.4", "babel-plugin-syntax-hermes-parser": "0.29.1", "babel-plugin-transform-flow-enums": "^0.0.2", "react-refresh": "^0.14.0" } }, "sha512-VYj0c/cTjQJn/RJ5G6P0L9wuYSbU9yGbPYDHCKstlQZQWkk+L9V8ZDbxdJBTIei9Xl3KPQ1odQ4QaeW+4v+AZg=="],
"expo/babel-preset-expo/babel-plugin-react-native-web": ["babel-plugin-react-native-web@0.21.1", "", {}, "sha512-7XywfJ5QIRMwjOL+pwJt2w47Jmi5fFLvK7/So4fV4jIN6PcRbylCp9/l3cJY4VJbSz3lnWTeHDTD1LKIc1C09Q=="],
@@ -3131,6 +3168,8 @@
"babel-preset-expo/babel-plugin-syntax-hermes-parser/hermes-parser/hermes-estree": ["hermes-estree@0.25.1", "", {}, "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw=="],
"expo-constants/@expo/config/@expo/config-plugins/slash": ["slash@3.0.0", "", {}, "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q=="],
"expo/babel-preset-expo/@react-native/babel-preset/@react-native/babel-plugin-codegen": ["@react-native/babel-plugin-codegen@0.81.4", "", { "dependencies": { "@babel/traverse": "^7.25.3", "@react-native/codegen": "0.81.4" } }, "sha512-6ztXf2Tl2iWznyI/Da/N2Eqymt0Mnn69GCLnEFxFbNdk0HxHPZBNWU9shTXhsLWOL7HATSqwg/bB1+3kY1q+mA=="],
"expo/babel-preset-expo/@react-native/babel-preset/babel-plugin-syntax-hermes-parser": ["babel-plugin-syntax-hermes-parser@0.29.1", "", { "dependencies": { "hermes-parser": "0.29.1" } }, "sha512-2WFYnoWGdmih1I1J5eIqxATOeycOqRwYxAQBu3cUu/rhwInwHUg7k60AFNbuGjSDL8tje5GDrAnxzRLcu2pYcA=="],

View File

@@ -0,0 +1,16 @@
import StackCustom from "../Stack/StackCustom";
import TextCustom from "../Text/TextCustom";
import BaseBox from "./BaseBox";
export default function ReportBox({ text }: { text: string }) {
return (
<BaseBox>
<StackCustom gap={"sm"}>
<TextCustom bold>
Catatan penolakan
</TextCustom>
<TextCustom>{text}</TextCustom>
</StackCustom>
</BaseBox>
);
}

View File

@@ -1,27 +1,39 @@
import { MainColor } from "@/constants/color-palet";
import React from "react";
import { StyleProp, StyleSheet, TextInput, View, ViewStyle } from "react-native";
import {
StyleProp,
StyleSheet,
View,
ViewStyle
} from "react-native";
import TextCustom from "../Text/TextCustom";
interface CircularInputProps {
value?: string | number
value?: string | number;
onChange?: (value: string | number) => void;
icon?: React.ReactNode;
style?: StyleProp<ViewStyle>
style?: StyleProp<ViewStyle>;
}
const CircularInput: React.FC<CircularInputProps> = ({ value, onChange, icon, style }) => {
const CircularInput: React.FC<CircularInputProps> = ({
value,
onChange,
icon,
style,
}) => {
return (
<View style={[styles.circleContainer, style]}>
{icon ? (
icon
) : (
<TextInput
value={String(value)}
onChangeText={onChange}
<TextCustom
// text={String(value)}
style={styles.input}
keyboardType="numeric"
maxLength={2} // Batasan maksimal karakter
/>
// keyboardType="numeric"
// maxLength={2} // Batasan maksimal karakter
>
{value}
</TextCustom>
)}
</View>
);
@@ -39,7 +51,7 @@ const styles = StyleSheet.create({
},
input: {
color: MainColor.yellow, // Warna kuning
fontSize: 24,
fontSize: 18,
fontWeight: "bold",
textAlign: "center",
padding: 0,

View File

@@ -196,6 +196,7 @@ const DateTimeInput_Android: React.FC<DateTimeInputProps> = ({
onChange={handleConfirmTime}
minimumDate={minimumDate}
maximumDate={maximumDate}
themeVariant="light"
/>
)}
</>

View File

@@ -145,6 +145,7 @@ const DateTimeInput_IOS: React.FC<DateTimeInputProps> = ({
onChange={handleConfirm}
minimumDate={minimumDate}
maximumDate={maximumDate}
themeVariant="light"
/>
</View>
</>

View File

@@ -27,6 +27,8 @@ type SelectProps = {
onChange: (value: string | number) => void;
borderRadius?: number;
styleContainer?: StyleProp<ViewStyle>;
allowClear?: boolean; // <-- new prop
clearLabel?: string; // default: "Kosongkan"
};
const SelectCustom: React.FC<SelectProps> = ({
@@ -39,13 +41,21 @@ const SelectCustom: React.FC<SelectProps> = ({
onChange,
borderRadius = 8,
styleContainer,
allowClear = true, // bisa dimatikan jika tidak perlu
clearLabel = "Hapus Pilihan",
}) => {
const [modalVisible, setModalVisible] = useState(false);
const selectedItem = data.find((item) => item.value === value);
const hasError = required && value === null; // <-- check if empty and required
// Gabungkan opsi clear di atas daftar
const renderData = allowClear
? [...data,
// { label: clearLabel, value: "__clear__" }
]
: data;
return (
<View style={[GStyles.inputContainerArea, styleContainer]}>
{label && (
@@ -54,29 +64,64 @@ const SelectCustom: React.FC<SelectProps> = ({
{required && <Text style={GStyles.inputRequired}> *</Text>}
</Text>
)}
<Pressable
{/* Input Container */}
<View
style={[
{ borderRadius, },
hasError ? GStyles.inputErrorBorder : null,
{
flexDirection: "row",
alignItems: "center",
// backgroundColor: "red",
// flex: 1,
borderRadius,
},
GStyles.inputContainerInput,
hasError ? GStyles.inputErrorBorder : null,
disabled && GStyles.disabledBox,
]} // <-- add error style
onPress={() => !disabled && setModalVisible(true)}
]}
>
<Text
style={
selectedItem
? disabled
? GStyles.inputTextDisabled
: GStyles.inputText
: disabled
? GStyles.inputPlaceholderDisabled
: GStyles.inputPlaceholder
}
<Pressable
style={[
{
flex: 1,
borderRadius,
flexDirection: "row",
alignItems: "center",
paddingHorizontal: 10,
height: 50,
},
// GStyles.inputContainerInput,
// hasError ? GStyles.inputErrorBorder : null,
// disabled && GStyles.disabledBox,
]}
onPress={() => !disabled && setModalVisible(true)}
>
{selectedItem?.label || placeholder}
</Text>
</Pressable>
<Text
style={
selectedItem
? disabled
? GStyles.inputTextDisabled
: GStyles.inputText
: disabled
? GStyles.inputPlaceholderDisabled
: GStyles.inputPlaceholder
}
>
{selectedItem?.label || placeholder}
</Text>
</Pressable>
{/* Tombol Clear (Hanya muncul jika ada nilai terpilih & allowClear aktif) */}
{!disabled && allowClear && value !== null && value !== undefined && (
<TouchableOpacity
style={{ paddingHorizontal: 10 }}
onPress={() => onChange(null as any)} // null dikirim sebagai value
>
<Text style={{ fontSize: 18, color: "#999" }}>×</Text>
</TouchableOpacity>
)}
</View>
<Modal visible={modalVisible} transparent animationType="fade">
<TouchableOpacity
@@ -86,19 +131,38 @@ const SelectCustom: React.FC<SelectProps> = ({
>
<View style={GStyles.selectModalContent}>
<FlatList
data={data}
data={renderData}
keyExtractor={(item) => String(item.value)}
renderItem={({ item }) => (
<TouchableOpacity
style={GStyles.selectOption}
onPress={() => {
onChange(item.value);
setModalVisible(false);
}}
>
<Text>{item.label}</Text>
</TouchableOpacity>
)}
renderItem={({ item }) => {
if (item.value === "__clear__") {
return (
<TouchableOpacity
style={[
GStyles.selectOption,
{ backgroundColor: "#fdd" },
]}
onPress={() => {
onChange(null as any); // kosongkan nilai
setModalVisible(false);
}}
>
<Text>{item.label}</Text>
</TouchableOpacity>
);
}
return (
<TouchableOpacity
style={GStyles.selectOption}
onPress={() => {
onChange(item.value);
setModalVisible(false);
}}
>
<Text>{item.label}</Text>
</TouchableOpacity>
);
}}
/>
</View>
</TouchableOpacity>

View File

@@ -23,7 +23,7 @@ interface TextCustomProps {
bold?: boolean;
semiBold?: boolean;
size?: "default" | "large" | "small" | "xlarge" | number
color?: "default" | "yellow" | "red" | "gray" | "green" | "black"
color?: "default" | "yellow" | "red" | "gray" | "green" | "black" | "orange"
align?: TextAlign; // Prop untuk alignment
truncate?: boolean | number;
onPress?: () => void;
@@ -62,6 +62,7 @@ const TextCustom: React.FC<TextCustomProps> = ({
else if (color === "gray") selectedStyles.push(styles.gray);
else if (color === "green") selectedStyles.push(styles.green);
else if (color === "black") selectedStyles.push(styles.black);
else if (color === "orange") selectedStyles.push(styles.orange);
// Alignment
if (align) {
@@ -140,4 +141,7 @@ export const styles = StyleSheet.create({
black: {
color: MainColor.black,
},
orange: {
color: MainColor.orange,
},
});

View File

@@ -14,7 +14,7 @@ export default function AdminButtonReject({
return (
<>
<ButtonCustom
iconLeft={<IconReject />}
iconLeft={<IconReject size={16} />}
backgroundColor={MainColor.red}
textColor="white"
onPress={onReject}

View File

@@ -4,9 +4,11 @@ import Grid from "@/components/Grid/GridCustom";
import { MainColor } from "@/constants/color-palet";
export default function AdminButtonReview({
isLoading,
onPublish,
onReject,
}: {
isLoading?: boolean;
onPublish: () => void;
onReject: () => void;
}) {
@@ -15,6 +17,7 @@ export default function AdminButtonReview({
<Grid>
<Grid.Col span={6} style={{ paddingRight: 10 }}>
<ButtonCustom
isLoading={isLoading}
iconLeft={<IconPublish />}
backgroundColor={MainColor.green}
textColor="white"

View File

@@ -0,0 +1,9 @@
import TextCustom from "../Text/TextCustom";
export default function NoDataText({ text }: { text?: string }) {
return (
<TextCustom color="gray" align="center" bold size={"small"}>
{text ? text : "Belum ada data"}
</TextCustom>
);
}

View File

@@ -12,7 +12,7 @@ import {
StyleProp,
ViewStyle,
} from "react-native";
import { SafeAreaView } from "react-native-safe-area-context";
import { NativeSafeAreaViewProps, SafeAreaView } from "react-native-safe-area-context";
interface ViewWrapperProps {
children: React.ReactNode;
@@ -21,6 +21,7 @@ interface ViewWrapperProps {
footerComponent?: React.ReactNode;
floatingButton?: React.ReactNode;
hideFooter?: boolean;
edgesFooter?: NativeSafeAreaViewProps["edges"];
style?: StyleProp<ViewStyle>;
}
@@ -37,6 +38,7 @@ const ViewWrapper = ({
footerComponent,
floatingButton,
hideFooter = false,
edgesFooter =[],
style,
}: ViewWrapperProps) => {
const assetBackground = require("../../assets/images/main-background.png");
@@ -77,7 +79,7 @@ const ViewWrapper = ({
{footerComponent ? (
<SafeAreaView
edges={["bottom"]}
edges={Platform.OS === "ios" ? edgesFooter : ["bottom"]}
style={{
backgroundColor: MainColor.darkblue,
height: OS_HEIGHT

View File

@@ -23,7 +23,7 @@ export {
// OS Height
const OS_ANDROID_HEIGHT = 115
const OS_IOS_HEIGHT = 65
const OS_IOS_HEIGHT = 70
const OS_HEIGHT = Platform.OS === "ios" ? OS_IOS_HEIGHT : OS_ANDROID_HEIGHT
// Text Size

View File

@@ -91,14 +91,11 @@ export const AuthProvider = ({ children }: { children: React.ReactNode }) => {
setToken(token);
await AsyncStorage.setItem("authToken", token);
const responseUser = await apiConfig.get(
`/mobile?token=${token}`,
{
headers: {
Authorization: `Bearer ${token}`,
},
}
);
const responseUser = await apiConfig.get(`/mobile?token=${token}`, {
headers: {
Authorization: `Bearer ${token}`,
},
});
const dataUser = responseUser.data.data;
setUser(dataUser);
@@ -147,9 +144,7 @@ export const AuthProvider = ({ children }: { children: React.ReactNode }) => {
await AsyncStorage.setItem("userData", JSON.stringify(dataUser));
return dataUser;
} catch (error: any) {
throw new Error(
error.response?.data?.message || "Gagal mengambil data user"
);
console.log(error.response?.data?.message + "user" || "Gagal mengambil data user");
} finally {
setIsLoading(false);
}

View File

@@ -1,7 +1,7 @@
{
"cli": {
"version": ">= 16.10.0",
"appVersionSource": "remote"
"appVersionSource": "local"
},
"build": {
"development": {
@@ -11,11 +11,19 @@
"preview": {
"distribution": "internal",
"android": {
"buildType": "apk"
"buildType": "app-bundle"
},
"ios": {
"simulator": false
}
},
"production": {
"autoIncrement": true
"android": {
"buildType": "app-bundle"
},
"ios": {
"resourceClass": "m1-medium"
}
}
},
"submit": {

29
google-services.json Normal file
View File

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

15
hipmi-note.md Normal file
View File

@@ -0,0 +1,15 @@
### Buil
eas build --profile production : for build production on expo with eas
npx expo prebuild : untuk build dan membuat folder android & ios
open ios/<nama-app>.xcworkspace : untuk membuka file xcode
Build ios : bun run ios
Build android : bun run android
Exp: open ios/HIPMIBADUNG.xcworkspace
### Other
perubahan versi : npm version patch
ios: bunx expo prebuild --platform ios
android: bunx expo prebuild --platform android

View File

@@ -1,5 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict/>
</plist>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 259 KiB

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