Compare commits
26 Commits
delete-acc
...
notificati
| Author | SHA1 | Date | |
|---|---|---|---|
| 1503707eed | |||
| a01a9bd93f | |||
| 05c1cac10f | |||
| d27c01ed56 | |||
| 34680a4c38 | |||
| 43c8c105cf | |||
| 2c0198b1b7 | |||
| 573b525352 | |||
| 6f9481c7c9 | |||
| cccb44a835 | |||
| 0f5862ce70 | |||
| 624bd49f69 | |||
| 2446e9d51a | |||
| ab5733f336 | |||
| f5e30087ed | |||
| a93f97ed6a | |||
| 858b441a8c | |||
| 98aaa126a1 | |||
| 69452ff4e7 | |||
| 33ec892ec8 | |||
| 8a900e9469 | |||
| d471682ae7 | |||
| 00eea71248 | |||
| 41e648d8f3 | |||
| 0c4deac6e2 | |||
| 676b8a38be |
@@ -82,6 +82,14 @@ def enableMinifyInReleaseBuilds = (findProperty('android.enableMinifyInReleaseBu
|
||||
def jscFlavor = 'io.github.react-native-community:jsc-android:2026004.+'
|
||||
|
||||
android {
|
||||
// @generated begin @rnmapbox/maps-libcpp - expo prebuild (DO NOT MODIFY) sync-e24830a5a3e854b398227dfe9630aabfaa1cadd1
|
||||
packagingOptions {
|
||||
pickFirst 'lib/x86/libc++_shared.so'
|
||||
pickFirst 'lib/x86_64/libc++_shared.so'
|
||||
pickFirst 'lib/arm64-v8a/libc++_shared.so'
|
||||
pickFirst 'lib/armeabi-v7a/libc++_shared.so'
|
||||
}
|
||||
// @generated end @rnmapbox/maps-libcpp
|
||||
ndkVersion rootProject.ext.ndkVersion
|
||||
|
||||
buildToolsVersion rootProject.ext.buildToolsVersion
|
||||
@@ -92,7 +100,7 @@ android {
|
||||
applicationId 'com.bip.hipmimobileapp'
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
versionCode 2
|
||||
versionCode 3
|
||||
versionName "1.0.1"
|
||||
|
||||
buildConfigField "String", "REACT_NATIVE_RELEASE_LEVEL", "\"${findProperty('reactNativeReleaseLevel') ?: 'stable'}\""
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools">
|
||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
|
||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
|
||||
<uses-permission android:name="android.permission.CAMERA"/>
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
||||
@@ -13,7 +15,11 @@
|
||||
<data android:scheme="https"/>
|
||||
</intent>
|
||||
</queries>
|
||||
<application android:name=".MainApplication" android:label="@string/app_name" android:icon="@mipmap/ic_launcher" android:roundIcon="@mipmap/ic_launcher_round" android:allowBackup="true" android:theme="@style/AppTheme" android:supportsRtl="true" android:enableOnBackInvokedCallback="false">
|
||||
<application android:name=".MainApplication" android:label="@string/app_name" android:icon="@mipmap/ic_launcher" android:roundIcon="@mipmap/ic_launcher_round" android:allowBackup="true" android:theme="@style/AppTheme" android:supportsRtl="true" android:enableOnBackInvokedCallback="false" android:fullBackupContent="@xml/secure_store_backup_rules" android:dataExtractionRules="@xml/secure_store_data_extraction_rules">
|
||||
<meta-data android:name="com.google.firebase.messaging.default_notification_color" android:resource="@color/notification_icon_color"/>
|
||||
<meta-data android:name="com.google.firebase.messaging.default_notification_icon" android:resource="@drawable/notification_icon"/>
|
||||
<meta-data android:name="expo.modules.notifications.default_notification_color" android:resource="@color/notification_icon_color"/>
|
||||
<meta-data android:name="expo.modules.notifications.default_notification_icon" android:resource="@drawable/notification_icon"/>
|
||||
<meta-data android:name="expo.modules.updates.ENABLED" android:value="false"/>
|
||||
<meta-data android:name="expo.modules.updates.EXPO_UPDATES_CHECK_ON_LAUNCH" android:value="ALWAYS"/>
|
||||
<meta-data android:name="expo.modules.updates.EXPO_UPDATES_LAUNCH_WAIT_MS" android:value="0"/>
|
||||
|
||||
BIN
android/app/src/main/res/drawable-hdpi/notification_icon.png
Normal file
BIN
android/app/src/main/res/drawable-hdpi/notification_icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.2 KiB |
BIN
android/app/src/main/res/drawable-mdpi/notification_icon.png
Normal file
BIN
android/app/src/main/res/drawable-mdpi/notification_icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.3 KiB |
BIN
android/app/src/main/res/drawable-xhdpi/notification_icon.png
Normal file
BIN
android/app/src/main/res/drawable-xhdpi/notification_icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.4 KiB |
BIN
android/app/src/main/res/drawable-xxhdpi/notification_icon.png
Normal file
BIN
android/app/src/main/res/drawable-xxhdpi/notification_icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.2 KiB |
BIN
android/app/src/main/res/drawable-xxxhdpi/notification_icon.png
Normal file
BIN
android/app/src/main/res/drawable-xxxhdpi/notification_icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.6 KiB |
@@ -3,4 +3,5 @@
|
||||
<color name="iconBackground">#ffffff</color>
|
||||
<color name="colorPrimary">#023c69</color>
|
||||
<color name="colorPrimaryDark">#ffffff</color>
|
||||
<color name="notification_icon_color">#ffffff</color>
|
||||
</resources>
|
||||
@@ -23,3 +23,25 @@ allprojects {
|
||||
|
||||
apply plugin: "expo-root-project"
|
||||
apply plugin: "com.facebook.react.rootproject"
|
||||
// @generated begin @rnmapbox/maps-v2-maven - expo prebuild (DO NOT MODIFY) sync-d4ccbfdff48fdba3138b02a8ba41b9722af001d8
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
maven {
|
||||
url 'https://api.mapbox.com/downloads/v2/releases/maven'
|
||||
// Authentication is no longer required as per Mapbox's removal of download token requirement
|
||||
// See: https://github.com/mapbox/mapbox-maps-flutter/issues/775
|
||||
// Keeping this as optional for backward compatibility
|
||||
def token = project.properties['MAPBOX_DOWNLOADS_TOKEN'] ?: System.getenv('RNMAPBOX_MAPS_DOWNLOAD_TOKEN')
|
||||
if (token) {
|
||||
authentication { basic(BasicAuthentication) }
|
||||
credentials {
|
||||
username = 'mapbox'
|
||||
password = token
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// @generated end @rnmapbox/maps-v2-maven
|
||||
@@ -14,11 +14,14 @@ export default {
|
||||
ios: {
|
||||
supportsTablet: true,
|
||||
bundleIdentifier: "com.anonymous.hipmi-mobile",
|
||||
googleServicesFile: "./ios/HIPMIBadungConnect/GoogleService-Info.plist",
|
||||
infoPlist: {
|
||||
ITSAppUsesNonExemptEncryption: false,
|
||||
NSLocationWhenInUseUsageDescription:
|
||||
"Aplikasi membutuhkan akses lokasi untuk menampilkan peta.",
|
||||
},
|
||||
associatedDomains: ["applinks:cld-dkr-staging-hipmi.wibudev.com"],
|
||||
buildNumber: "7",
|
||||
buildNumber: "16",
|
||||
},
|
||||
|
||||
android: {
|
||||
@@ -29,7 +32,7 @@ export default {
|
||||
},
|
||||
edgeToEdgeEnabled: true,
|
||||
package: "com.bip.hipmimobileapp",
|
||||
versionCode: 2,
|
||||
versionCode: 3,
|
||||
// softwareKeyboardLayoutMode: 'resize', // option: untuk mengatur keyboard pada room chst collaboration
|
||||
intentFilters: [
|
||||
{
|
||||
@@ -55,7 +58,6 @@ export default {
|
||||
|
||||
plugins: [
|
||||
"expo-router",
|
||||
"expo-notifications",
|
||||
"expo-web-browser",
|
||||
[
|
||||
"expo-splash-screen",
|
||||
@@ -75,6 +77,16 @@ export default {
|
||||
},
|
||||
],
|
||||
"expo-font",
|
||||
"@rnmapbox/maps",
|
||||
"@react-native-firebase/app",
|
||||
[
|
||||
"expo-notifications",
|
||||
{
|
||||
icon: "./assets/images/icon.png",
|
||||
color: "#ffffff",
|
||||
iosDisplayInForeground: true,
|
||||
},
|
||||
],
|
||||
],
|
||||
|
||||
experiments: {
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { BackButton } from "@/components";
|
||||
import { IconPlus } from "@/components/_Icon";
|
||||
import { IconDot } from "@/components/_Icon/IconComponent";
|
||||
import LeftButtonCustom from "@/components/Button/BackButton";
|
||||
import { MainColor } from "@/constants/color-palet";
|
||||
import { ICON_SIZE_SMALL } from "@/constants/constans-value";
|
||||
@@ -56,6 +58,12 @@ export default function UserLayout() {
|
||||
options={{
|
||||
title: "Notifikasi",
|
||||
headerLeft: () => <BackButton />,
|
||||
headerRight: () => (
|
||||
<IconPlus
|
||||
color={MainColor.yellow}
|
||||
onPress={() => router.push("/test-notifications")}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -595,6 +603,13 @@ export default function UserLayout() {
|
||||
headerLeft: () => <BackButton />,
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="forum/terms"
|
||||
options={{
|
||||
title: "Syarat & Ketentuan Forum",
|
||||
headerLeft: () => <BackButton />,
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* ========== Maps Section ========= */}
|
||||
<Stack.Screen
|
||||
|
||||
@@ -155,7 +155,7 @@ export default function CollaborationCreate() {
|
||||
<TextAreaCustom
|
||||
required
|
||||
label="Keuntungan Proyek"
|
||||
placeholder="Masukan keuntungan proyek"
|
||||
placeholder="Masukan keuntungan proyek, contoh: Meningkatkan relasi bisnis , menjamin kualitas produk, meningkatkan kinerja dan lain lain"
|
||||
showCount
|
||||
maxLength={1000}
|
||||
value={data?.benefit}
|
||||
|
||||
@@ -19,6 +19,7 @@ export default function DeleteAccount() {
|
||||
const { token, logout, user } = useAuth();
|
||||
const { phone } = useLocalSearchParams();
|
||||
const [text, setText] = useState("");
|
||||
const [isLoading, setLoading] = useState(false);
|
||||
|
||||
const deleteAccount = async () => {
|
||||
if (text !== "Delete Account") {
|
||||
@@ -29,15 +30,38 @@ export default function DeleteAccount() {
|
||||
}
|
||||
|
||||
AlertDefaultSystem({
|
||||
title: "Apakah anda yakin ingin menghapus akun ini?",
|
||||
title: "Anda yakin akan menghapus akun ini?",
|
||||
message:
|
||||
"Semua data yang pernah anda buat akan terhapus secara permanen !",
|
||||
textLeft: "Batal",
|
||||
textRight: "Ya",
|
||||
onPressRight: async () => {
|
||||
const response = await apiDeleteUser({ id: user?.id as string });
|
||||
console.log("RESPONSE >> ", response);
|
||||
logout();
|
||||
try {
|
||||
setLoading(true);
|
||||
const response = await apiDeleteUser({ id: user?.id as string });
|
||||
|
||||
if (response.success) {
|
||||
console.log("RESPONSE >> ", response);
|
||||
Toast.show({
|
||||
type: "success",
|
||||
text1: "Akun berhasil dihapus",
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
logout();
|
||||
setLoading(false);
|
||||
}, 2000);
|
||||
} else {
|
||||
Toast.show({
|
||||
type: "error",
|
||||
text1: "Gagal menghapus akun",
|
||||
});
|
||||
setLoading(false);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("ERROR >> ", error);
|
||||
setLoading(false);
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
@@ -73,6 +97,8 @@ export default function DeleteAccount() {
|
||||
backgroundColor="red"
|
||||
textColor="white"
|
||||
onPress={deleteAccount}
|
||||
isLoading={isLoading}
|
||||
disabled={isLoading}
|
||||
>
|
||||
Submit
|
||||
</ButtonCustom>
|
||||
|
||||
@@ -18,7 +18,7 @@ import {
|
||||
import { apiMasterEventType } from "@/service/api-client/api-master";
|
||||
import { DateTimePickerEvent } from "@react-native-community/datetimepicker";
|
||||
import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import Toast from "react-native-toast-message";
|
||||
|
||||
export default function EventEdit() {
|
||||
@@ -55,6 +55,7 @@ export default function EventEdit() {
|
||||
try {
|
||||
setIsLoadData(true);
|
||||
const response = await apiEventGetOne({ id: id as string });
|
||||
console.log("[DATA BY ID]", JSON.stringify(response, null, 2));
|
||||
if (response.success) {
|
||||
setData(response.data);
|
||||
setSelectedDate(new Date(response.data.tanggal));
|
||||
@@ -209,7 +210,7 @@ export default function EventEdit() {
|
||||
minimumDate={new Date(Date.now())}
|
||||
label="Tanggal & Waktu Mulai"
|
||||
required
|
||||
value={selectedDate as any}
|
||||
value={selectedDate}
|
||||
onChange={(date: any) => {
|
||||
setSelectedDate(date as any);
|
||||
}}
|
||||
@@ -254,7 +255,6 @@ export default function EventEdit() {
|
||||
placeholder="Masukkan deskripsi event"
|
||||
required
|
||||
showCount
|
||||
maxLength={100}
|
||||
value={data?.deskripsi}
|
||||
onChangeText={(value) => setData({ ...data, deskripsi: value })}
|
||||
/>
|
||||
|
||||
@@ -71,8 +71,6 @@ export default function EventDetailPublish() {
|
||||
}
|
||||
}
|
||||
|
||||
console.log("[participans]", isParticipant);
|
||||
|
||||
const handlePress = (item: IMenuDrawerItem) => {
|
||||
console.log("PATH ", item.path);
|
||||
router.navigate(item.path as any);
|
||||
@@ -139,7 +137,7 @@ export default function EventDetailPublish() {
|
||||
<>
|
||||
<Stack.Screen
|
||||
options={{
|
||||
title: `Event publish`,
|
||||
title: `Event Publish`,
|
||||
headerLeft: () => <LeftButtonCustom />,
|
||||
headerRight: () => <DotButton onPress={() => setOpenDrawer(true)} />,
|
||||
}}
|
||||
|
||||
@@ -110,13 +110,14 @@ export default function EventCreate() {
|
||||
const response = await apiEventCreate(newData);
|
||||
console.log("Response", JSON.stringify(response, null, 2));
|
||||
|
||||
router.navigate("/event/status");
|
||||
router.replace("/event/status");
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const buttonSubmit = (
|
||||
<ButtonCustom
|
||||
@@ -144,7 +145,7 @@ export default function EventCreate() {
|
||||
label: item.name,
|
||||
value: item.id,
|
||||
}))}
|
||||
value={data?.eventMaster_TipeAcaraId || ""}
|
||||
value={data?.eventMaster_TipeAcaraId || null}
|
||||
onChange={(value: any) =>
|
||||
setData({ ...data, eventMaster_TipeAcaraId: value })
|
||||
}
|
||||
@@ -191,7 +192,7 @@ export default function EventCreate() {
|
||||
placeholder="Masukkan deskripsi event"
|
||||
required
|
||||
showCount
|
||||
maxLength={1000}
|
||||
value={data?.deskripsi || ""}
|
||||
onChangeText={(value: any) =>
|
||||
setData({ ...data, deskripsi: value })
|
||||
}
|
||||
|
||||
@@ -5,9 +5,12 @@ import {
|
||||
TextAreaCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import AlertWarning from "@/components/Alert/AlertWarning";
|
||||
import { apiForumGetOne, apiForumUpdate } from "@/service/api-client/api-forum";
|
||||
import { isBadContent } from "@/utils/badWordsIndonesia";
|
||||
import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
|
||||
import { useCallback, useState } from "react";
|
||||
import { Alert } from "react-native";
|
||||
import Toast from "react-native-toast-message";
|
||||
|
||||
export default function ForumEdit() {
|
||||
@@ -43,6 +46,12 @@ export default function ForumEdit() {
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (isBadContent(text)) {
|
||||
AlertWarning({});
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
setIsLoading(true);
|
||||
const response = await apiForumUpdate({
|
||||
|
||||
@@ -1,142 +1,12 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
AvatarComp,
|
||||
ButtonCustom,
|
||||
CenterCustom,
|
||||
DrawerCustom,
|
||||
FloatingButton,
|
||||
Grid,
|
||||
LoaderCustom,
|
||||
StackCustom,
|
||||
TextCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
import Forum_BoxDetailSection from "@/screens/Forum/DiscussionBoxSection";
|
||||
import Forum_MenuDrawerBerandaSection from "@/screens/Forum/MenuDrawerSection.tsx/MenuBeranda";
|
||||
import { apiForumGetAll } from "@/service/api-client/api-forum";
|
||||
import { apiUser } from "@/service/api-client/api-user";
|
||||
import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
|
||||
import _ from "lodash";
|
||||
import { useCallback, useState } from "react";
|
||||
import View_Forumku from "@/screens/Forum/ViewForumku";
|
||||
import View_Forumku2 from "@/screens/Forum/ViewForumku2";
|
||||
|
||||
export default function Forumku() {
|
||||
const { id } = useLocalSearchParams();
|
||||
const { user } = useAuth();
|
||||
const [openDrawer, setOpenDrawer] = useState(false);
|
||||
const [status, setStatus] = useState("");
|
||||
const [listData, setListData] = useState<any | null>(null);
|
||||
const [dataUser, setDataUser] = useState<any | null>(null);
|
||||
const [loadingGetList, setLoadingGetList] = useState(false);
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
onLoadData();
|
||||
onLoadDataProfile(id as string);
|
||||
}, [id])
|
||||
);
|
||||
|
||||
const onLoadDataProfile = async (id: string) => {
|
||||
try {
|
||||
const response = await apiUser(id);
|
||||
|
||||
setDataUser(response.data);
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
} finally {
|
||||
}
|
||||
};
|
||||
|
||||
const onLoadData = async () => {
|
||||
try {
|
||||
setLoadingGetList(true);
|
||||
const response = await apiForumGetAll({
|
||||
search: "",
|
||||
authorId: id as string,
|
||||
});
|
||||
|
||||
setListData(response.data);
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
} finally {
|
||||
setLoadingGetList(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<ViewWrapper
|
||||
floatingButton={
|
||||
user?.id === id && (
|
||||
<FloatingButton
|
||||
onPress={() =>
|
||||
router.navigate("/(application)/(user)/forum/create")
|
||||
}
|
||||
/>
|
||||
)
|
||||
}
|
||||
>
|
||||
<StackCustom>
|
||||
<CenterCustom>
|
||||
<AvatarComp
|
||||
fileId={dataUser?.Profile?.imageId}
|
||||
href={`/(application)/(image)/preview-image/${dataUser?.Profile?.imageId}`}
|
||||
size="xl"
|
||||
/>
|
||||
</CenterCustom>
|
||||
|
||||
<Grid>
|
||||
<Grid.Col span={6}>
|
||||
<TextCustom bold truncate>
|
||||
@{dataUser?.username || "-"}
|
||||
</TextCustom>
|
||||
<TextCustom>{listData?.length || "0"} postingan</TextCustom>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={6} style={{ alignItems: "flex-end" }}>
|
||||
<ButtonCustom href={`/profile/${dataUser?.Profile?.id}`}>
|
||||
Kunjungi Profile
|
||||
</ButtonCustom>
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
{loadingGetList ? (
|
||||
<LoaderCustom />
|
||||
) : _.isEmpty(listData) ? (
|
||||
<TextCustom> Tidak ada diskusi</TextCustom>
|
||||
) : (
|
||||
<>
|
||||
{listData?.map((item: any, index: number) => (
|
||||
<Forum_BoxDetailSection
|
||||
isRightComponent={false}
|
||||
key={index}
|
||||
data={item}
|
||||
isTruncate={true}
|
||||
href={`/forum/${item.id}`}
|
||||
onSetData={(value) => {
|
||||
setOpenDrawer(value.setOpenDrawer);
|
||||
setStatus(value.setStatus);
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</StackCustom>
|
||||
</ViewWrapper>
|
||||
|
||||
{/* Drawer Komponen Eksternal */}
|
||||
<DrawerCustom
|
||||
height={"auto"}
|
||||
isVisible={openDrawer}
|
||||
closeDrawer={() => setOpenDrawer(false)}
|
||||
>
|
||||
<Forum_MenuDrawerBerandaSection
|
||||
id={id as string}
|
||||
status={status}
|
||||
setIsDrawerOpen={() => {
|
||||
setOpenDrawer(false);
|
||||
}}
|
||||
authorId={id as string}
|
||||
/>
|
||||
</DrawerCustom>
|
||||
{/* <View_Forumku /> */}
|
||||
<View_Forumku2 />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
TextCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import AlertWarning from "@/components/Alert/AlertWarning";
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
import Forum_CommentarBoxSection from "@/screens/Forum/CommentarBoxSection";
|
||||
import Forum_BoxDetailSection from "@/screens/Forum/DiscussionBoxSection";
|
||||
@@ -18,9 +19,11 @@ import {
|
||||
apiForumGetOne,
|
||||
apiForumUpdateStatus,
|
||||
} from "@/service/api-client/api-forum";
|
||||
import { isBadContent } from "@/utils/badWordsIndonesia";
|
||||
import { useFocusEffect, useLocalSearchParams } from "expo-router";
|
||||
import _ from "lodash";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { Alert } from "react-native";
|
||||
|
||||
interface CommentProps {
|
||||
id: string;
|
||||
@@ -110,11 +113,15 @@ export default function ForumDetail() {
|
||||
|
||||
// Create Commentar
|
||||
const handlerCreateCommentar = async () => {
|
||||
if (isBadContent(text)) {
|
||||
AlertWarning({});
|
||||
return;
|
||||
}
|
||||
|
||||
const newData = {
|
||||
comment: text,
|
||||
authorId: user?.id,
|
||||
};
|
||||
|
||||
try {
|
||||
setLoadingComment(true);
|
||||
const response = await apiForumCreateComment({
|
||||
@@ -223,6 +230,7 @@ export default function ForumDetail() {
|
||||
>
|
||||
<Forum_MenuDrawerBerandaSection
|
||||
id={dataId}
|
||||
authorUsername={data?.Author?.username as string}
|
||||
status={status}
|
||||
setIsDrawerOpen={() => {
|
||||
setOpenDrawer(false);
|
||||
|
||||
@@ -2,12 +2,15 @@ import {
|
||||
BoxButtonOnFooter,
|
||||
ButtonCustom,
|
||||
TextAreaCustom,
|
||||
ViewWrapper,
|
||||
ViewWrapper
|
||||
} from "@/components";
|
||||
import AlertWarning from "@/components/Alert/AlertWarning";
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
import { apiForumCreate } from "@/service/api-client/api-forum";
|
||||
import { isBadContent } from "@/utils/badWordsIndonesia";
|
||||
import { router } from "expo-router";
|
||||
import { useState } from "react";
|
||||
import { Alert } from "react-native";
|
||||
import Toast from "react-native-toast-message";
|
||||
|
||||
export default function ForumCreate() {
|
||||
@@ -16,11 +19,16 @@ export default function ForumCreate() {
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const handlerSubmit = async () => {
|
||||
|
||||
if (isBadContent(text)) {
|
||||
AlertWarning({})
|
||||
return;
|
||||
}
|
||||
|
||||
const newData = {
|
||||
diskusi: text,
|
||||
authorId: user?.id,
|
||||
};
|
||||
|
||||
try {
|
||||
setIsLoading(true);
|
||||
const response = await apiForumCreate({ data: newData });
|
||||
|
||||
@@ -1,129 +1,12 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
AvatarComp,
|
||||
BackButton,
|
||||
DrawerCustom,
|
||||
LoaderCustom,
|
||||
SearchInput,
|
||||
TextCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import FloatingButton from "@/components/Button/FloatingButton";
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
import Forum_BoxDetailSection from "@/screens/Forum/DiscussionBoxSection";
|
||||
import Forum_MenuDrawerBerandaSection from "@/screens/Forum/MenuDrawerSection.tsx/MenuBeranda";
|
||||
import { apiForumGetAll } from "@/service/api-client/api-forum";
|
||||
import { apiUser } from "@/service/api-client/api-user";
|
||||
import { router, Stack, useFocusEffect } from "expo-router";
|
||||
import _ from "lodash";
|
||||
import { useCallback, useState } from "react";
|
||||
import Forum_ViewBeranda from "@/screens/Forum/ViewBeranda";
|
||||
import Forum_ViewBeranda2 from "@/screens/Forum/ViewBeranda2";
|
||||
|
||||
export default function Forum() {
|
||||
const [openDrawer, setOpenDrawer] = useState(false);
|
||||
const [status, setStatus] = useState("");
|
||||
const { user } = useAuth();
|
||||
const [dataUser, setDataUser] = useState<any>();
|
||||
const [listData, setListData] = useState<any[]>();
|
||||
const [loadingGetList, setLoadingGetList] = useState(false);
|
||||
const [search, setSearch] = useState("");
|
||||
const [dataId, setDataId] = useState("");
|
||||
const [authorId, setAuthorId] = useState("");
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
onLoadData();
|
||||
onLoadDataProfile(user?.id as string);
|
||||
}, [user?.id, search])
|
||||
);
|
||||
|
||||
const onLoadDataProfile = async (id: string) => {
|
||||
const response = await apiUser(id);
|
||||
setDataUser(response.data);
|
||||
};
|
||||
|
||||
const onLoadData = async () => {
|
||||
try {
|
||||
setLoadingGetList(true);
|
||||
const response = await apiForumGetAll({ search: search });
|
||||
|
||||
setListData(response.data);
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
} finally {
|
||||
setLoadingGetList(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Stack.Screen
|
||||
options={{
|
||||
title: "Forum",
|
||||
headerLeft: () => <BackButton />,
|
||||
headerRight: () => (
|
||||
<AvatarComp
|
||||
fileId={dataUser?.Profile?.imageId}
|
||||
size="base"
|
||||
href={`/forum/${user?.id}/forumku`}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
|
||||
<ViewWrapper
|
||||
headerComponent={
|
||||
<SearchInput
|
||||
placeholder="Cari topik diskusi"
|
||||
onChangeText={(e) => setSearch(e)}
|
||||
/>
|
||||
}
|
||||
floatingButton={
|
||||
<FloatingButton
|
||||
onPress={() =>
|
||||
router.navigate("/(application)/(user)/forum/create")
|
||||
}
|
||||
/>
|
||||
}
|
||||
>
|
||||
{loadingGetList ? (
|
||||
<LoaderCustom />
|
||||
) : _.isEmpty(listData) ? (
|
||||
<TextCustom align="center" color="gray">
|
||||
Tidak ada diskusi
|
||||
</TextCustom>
|
||||
) : (
|
||||
listData?.map((e: any, i: number) => (
|
||||
<Forum_BoxDetailSection
|
||||
key={i}
|
||||
data={e}
|
||||
onSetData={() => {
|
||||
setDataId(e.id);
|
||||
setOpenDrawer(true);
|
||||
setStatus(e.ForumMaster_StatusPosting?.status);
|
||||
setAuthorId(e.Author?.id);
|
||||
}}
|
||||
isTruncate={true}
|
||||
href={`/forum/${e.id}`}
|
||||
isRightComponent={false}
|
||||
/>
|
||||
))
|
||||
)}
|
||||
</ViewWrapper>
|
||||
|
||||
<DrawerCustom
|
||||
height={"auto"}
|
||||
isVisible={openDrawer}
|
||||
closeDrawer={() => setOpenDrawer(false)}
|
||||
>
|
||||
<Forum_MenuDrawerBerandaSection
|
||||
id={dataId}
|
||||
authorId={authorId}
|
||||
status={status}
|
||||
setIsDrawerOpen={() => {
|
||||
setOpenDrawer(false);
|
||||
}}
|
||||
/>
|
||||
</DrawerCustom>
|
||||
{/* <Forum_ViewBeranda /> */}
|
||||
<Forum_ViewBeranda2 />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
202
app/(application)/(user)/forum/terms.tsx
Normal file
202
app/(application)/(user)/forum/terms.tsx
Normal file
@@ -0,0 +1,202 @@
|
||||
import {
|
||||
BaseBox,
|
||||
ButtonCustom,
|
||||
CheckboxCustom,
|
||||
NewWrapper,
|
||||
StackCustom,
|
||||
TextCustom,
|
||||
} from "@/components";
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
import { apiAcceptForumTerms } from "@/service/api-client/api-user";
|
||||
import { GStyles } from "@/styles/global-styles";
|
||||
import { Ionicons } from "@expo/vector-icons";
|
||||
import { router } from "expo-router";
|
||||
import { useState } from "react";
|
||||
import { View } from "react-native";
|
||||
import { Text } from "react-native-paper";
|
||||
import Toast from "react-native-toast-message";
|
||||
|
||||
export default function ForumSplash() {
|
||||
const { user } = useAuth();
|
||||
const [term, setTerm] = useState(false);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const handleSubmit = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const respone = await apiAcceptForumTerms({
|
||||
category: "Forum",
|
||||
userId: user?.id as any,
|
||||
});
|
||||
|
||||
if (respone.success) {
|
||||
Toast.show({
|
||||
type: "success",
|
||||
text1: "Berhasil",
|
||||
text2: "Terima kasih telah menerima syarat & ketentuan forum ini",
|
||||
});
|
||||
|
||||
router.replace("/(application)/forum");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
Toast.show({
|
||||
type: "error",
|
||||
text1: "Gagal",
|
||||
text2: "Terjadi kesalahan, silahkan coba lagi",
|
||||
});
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<NewWrapper>
|
||||
{/* <TextCustom bold>HIPMI Badung Connect</TextCustom> . */}
|
||||
|
||||
<BaseBox>
|
||||
<StackCustom>
|
||||
<TextCustom>
|
||||
Dengan mengakses dan menggunakan Forum HIPMI Badung Connect, Anda
|
||||
secara sadar menyetujui ketentuan berikut:
|
||||
</TextCustom>
|
||||
|
||||
<TextCustom bold>
|
||||
1. Dilarang keras memposting konten yang mengandung:
|
||||
</TextCustom>
|
||||
<View style={{ paddingInline: 10 }}>
|
||||
{forumTerms1.map((term, index) => (
|
||||
<View
|
||||
key={index}
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
gap: 10,
|
||||
paddingBottom: 10,
|
||||
}}
|
||||
>
|
||||
<Ionicons name="radio-button-on" color={"white"} />
|
||||
<TextCustom>{term.text}</TextCustom>
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
|
||||
<TextCustom bold>
|
||||
2. Setiap pengguna bertanggung jawab penuh atas konten yang
|
||||
diunggah. Konten yang melanggar ketentuan ini dapat dihapus kapan
|
||||
saja tanpa pemberitahuan.
|
||||
</TextCustom>
|
||||
|
||||
<TextCustom bold>
|
||||
3. Jika Anda menemukan konten tidak pantas, segera:
|
||||
</TextCustom>
|
||||
<View style={{ paddingInline: 10 }}>
|
||||
{forumTerms2.map((term, index) => (
|
||||
<View
|
||||
key={index}
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
gap: 10,
|
||||
paddingBottom: 10,
|
||||
}}
|
||||
>
|
||||
<Ionicons name="radio-button-on" color={"white"} />
|
||||
<TextCustom>{term.text}</TextCustom>
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
|
||||
<TextCustom bold>
|
||||
4. Gunakan fitur “Blokir Pengguna” di profil pengguna terkait
|
||||
</TextCustom>
|
||||
<View style={{ paddingInline: 10 }}>
|
||||
{forumTerms3.map((term, index) => (
|
||||
<View
|
||||
key={index}
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
gap: 10,
|
||||
paddingBottom: 10,
|
||||
}}
|
||||
>
|
||||
<Ionicons name="radio-button-on" color={"white"} />
|
||||
<TextCustom>{term.text}</TextCustom>
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
|
||||
<TextCustom>
|
||||
Pelanggaran terhadap ketentuan ini berakibat{" "}
|
||||
<TextCustom bold>pencabutan akses</TextCustom> ke Forum dan/atau{" "}
|
||||
<TextCustom bold>pemblokiran akun secara permanen</TextCustom> tanpa
|
||||
pemberitahuan lebih lanjut.
|
||||
</TextCustom>
|
||||
|
||||
<View
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
marginTop: 16,
|
||||
marginBottom: 16,
|
||||
}}
|
||||
>
|
||||
<CheckboxCustom value={term} onChange={() => setTerm(!term)} />
|
||||
|
||||
<Text style={GStyles.textLabel}>
|
||||
Saya telah membaca dan menyetujui Syarat & Ketentuan Forum ini
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
<ButtonCustom
|
||||
disabled={!term || loading}
|
||||
onPress={() => {
|
||||
handleSubmit();
|
||||
}}
|
||||
>
|
||||
Lanjut
|
||||
</ButtonCustom>
|
||||
</StackCustom>
|
||||
</BaseBox>
|
||||
</NewWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
// Data dalam format JSON (bisa juga diimpor dari file terpisah)
|
||||
interface Term {
|
||||
text: string;
|
||||
}
|
||||
|
||||
const forumTerms1: Term[] = [
|
||||
{
|
||||
text: "Ujaran kebencian, diskriminasi, atau konten SARA (Suku, Agama, Ras, Antar-golongan)",
|
||||
},
|
||||
{ text: "Kata kasar, pelecehan, ancaman, atau bullying" },
|
||||
{ text: "Pornografi, hoaks, spam, atau informasi menyesatkan" },
|
||||
{ text: "Promosi aktivitas ilegal seperti perjudian atau narkoba" },
|
||||
];
|
||||
|
||||
const forumTerms2: Term[] = [
|
||||
{
|
||||
text: "Gunakan tombol “Laporkan” di setiap postingan, atau",
|
||||
},
|
||||
{
|
||||
text: "Gunakan fitur “Blokir Pengguna” di profil pengguna terkait.",
|
||||
},
|
||||
];
|
||||
|
||||
const forumTerms3: Term[] = [
|
||||
{
|
||||
text: "Meninjau setiap laporan dalam waktu 24 jam",
|
||||
},
|
||||
{
|
||||
text: "Menghapus konten yang melanggar",
|
||||
},
|
||||
{
|
||||
text: "Memblokir akun pelanggar sesuai tingkat pelanggaran",
|
||||
},
|
||||
];
|
||||
@@ -1,9 +1,10 @@
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import { StackCustom, ViewWrapper } from "@/components";
|
||||
import { ButtonCustom, StackCustom, ViewWrapper } from "@/components";
|
||||
import { MainColor } from "@/constants/color-palet";
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
import Home_BottomFeatureSection from "@/screens/Home/bottomFeatureSection";
|
||||
import HeaderBell from "@/screens/Home/HeaderBell";
|
||||
import Home_ImageSection from "@/screens/Home/imageSection";
|
||||
import TabSection from "@/screens/Home/tabSection";
|
||||
import { tabsHome } from "@/screens/Home/tabsList";
|
||||
@@ -11,31 +12,50 @@ import Home_FeatureSection from "@/screens/Home/topFeatureSection";
|
||||
import { apiUser } from "@/service/api-client/api-user";
|
||||
import { apiVersion } from "@/service/api-config";
|
||||
import { Ionicons } from "@expo/vector-icons";
|
||||
import { Redirect, router, Stack } from "expo-router";
|
||||
import { useEffect, useState } from "react";
|
||||
import { Redirect, router, Stack, useFocusEffect } from "expo-router";
|
||||
import { useCallback, useState } from "react";
|
||||
import { RefreshControl } from "react-native";
|
||||
|
||||
export default function Application() {
|
||||
const { token, user } = useAuth();
|
||||
|
||||
const { token, user, userData } = useAuth();
|
||||
const [data, setData] = useState<any>();
|
||||
const [refreshing, setRefreshing] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
onLoadData();
|
||||
checkVersion();
|
||||
}, []);
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
onLoadData();
|
||||
checkVersion();
|
||||
userData(token as string);
|
||||
}, [user?.id, token])
|
||||
);
|
||||
|
||||
async function onLoadData() {
|
||||
const response = await apiUser(user?.id as string);
|
||||
console.log("Response profile >>", JSON.stringify(response?.data?.Profile, null, 2));
|
||||
console.log(
|
||||
"[Profile ID]>>",
|
||||
JSON.stringify(response?.data?.Profile?.id, null, 2)
|
||||
);
|
||||
|
||||
setData(response.data);
|
||||
}
|
||||
|
||||
const checkVersion = async () => {
|
||||
const response = await apiVersion();
|
||||
console.log("Version >>", JSON.stringify(response.data, null, 2));
|
||||
console.log("[Version] >>", JSON.stringify(response.data, null, 2));
|
||||
};
|
||||
|
||||
const onRefresh = useCallback(() => {
|
||||
setRefreshing(true);
|
||||
onLoadData();
|
||||
checkVersion();
|
||||
setRefreshing(false);
|
||||
}, []);
|
||||
|
||||
if (user && user?.termsOfServiceAccepted === false) {
|
||||
console.log("User is not accept term service");
|
||||
return <Redirect href={`/terms-agreement`} />;
|
||||
}
|
||||
|
||||
if (data && data?.active === false) {
|
||||
console.log("User is not active");
|
||||
return <Redirect href={`/waiting-room`} />;
|
||||
@@ -61,24 +81,27 @@ export default function Application() {
|
||||
}}
|
||||
/>
|
||||
),
|
||||
headerRight: () => (
|
||||
<Ionicons
|
||||
name="notifications"
|
||||
size={20}
|
||||
color={MainColor.yellow}
|
||||
onPress={() => {
|
||||
router.push("/notifications");
|
||||
}}
|
||||
/>
|
||||
),
|
||||
headerRight: () => <HeaderBell />,
|
||||
}}
|
||||
/>
|
||||
<ViewWrapper
|
||||
refreshControl={
|
||||
<RefreshControl refreshing={refreshing} onRefresh={onRefresh} />
|
||||
}
|
||||
footerComponent={
|
||||
<TabSection tabs={tabsHome(data?.Profile?.id as string)} />
|
||||
<TabSection
|
||||
tabs={tabsHome({
|
||||
acceptedForumTermsAt: data?.acceptedForumTermsAt,
|
||||
profileId: data?.Profile?.id,
|
||||
})}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<StackCustom>
|
||||
{/* <ButtonCustom onPress={() => router.push("./test-notifications")}>
|
||||
Test Notif
|
||||
</ButtonCustom> */}
|
||||
|
||||
<Home_ImageSection />
|
||||
|
||||
<Home_FeatureSection />
|
||||
|
||||
@@ -15,6 +15,7 @@ import Investment_ButtonInvestasiSection from "@/screens/Invesment/ButtonInvesta
|
||||
import Invesment_ComponentBoxOnBottomDetail from "@/screens/Invesment/ComponentBoxOnBottomDetail";
|
||||
import Invesment_DetailDataPublishSection from "@/screens/Invesment/DetailDataPublishSection";
|
||||
import { apiInvestmentGetOne } from "@/service/api-client/api-investment";
|
||||
import { countDownAndCondition } from "@/utils/countDownAndCondition";
|
||||
import { AntDesign, MaterialIcons } from "@expo/vector-icons";
|
||||
import {
|
||||
router,
|
||||
@@ -23,7 +24,7 @@ import {
|
||||
useLocalSearchParams,
|
||||
} from "expo-router";
|
||||
import _ from "lodash";
|
||||
import { useCallback, useState } from "react";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
|
||||
export default function InvestmentDetail() {
|
||||
const { user } = useAuth();
|
||||
@@ -62,6 +63,31 @@ export default function InvestmentDetail() {
|
||||
setOpenDrawerPublish(false);
|
||||
};
|
||||
|
||||
const [value, setValue] = useState({
|
||||
sisa: 0,
|
||||
reminder: false,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
updateCountDown();
|
||||
}, [data]);
|
||||
|
||||
console.log("[DATA DETAIL]", JSON.stringify(data, null, 2));
|
||||
|
||||
const updateCountDown = () => {
|
||||
const countDown = countDownAndCondition({
|
||||
duration: data?.MasterPencarianInvestor.name,
|
||||
publishTime: data?.countDown,
|
||||
});
|
||||
|
||||
setValue({
|
||||
sisa: countDown.durationDay,
|
||||
reminder: countDown.reminder,
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
|
||||
const bottomSection = (
|
||||
<Invesment_ComponentBoxOnBottomDetail
|
||||
id={id as string}
|
||||
@@ -71,7 +97,7 @@ export default function InvestmentDetail() {
|
||||
);
|
||||
|
||||
const buttonSection = (
|
||||
<Investment_ButtonInvestasiSection id={id as string} isMine={user?.id === data?.author?.id} />
|
||||
<Investment_ButtonInvestasiSection id={id as string} isMine={user?.id === data?.author?.id} reminder={value.reminder} />
|
||||
);
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,32 +1,6 @@
|
||||
import {
|
||||
ButtonCustom,
|
||||
DrawerCustom,
|
||||
DummyLandscapeImage,
|
||||
Grid,
|
||||
Spacing,
|
||||
StackCustom,
|
||||
TextCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import GridTwoView from "@/components/_ShareComponent/GridTwoView";
|
||||
import API_IMAGE from "@/constants/api-storage";
|
||||
import { ICON_SIZE_SMALL } from "@/constants/constans-value";
|
||||
import { apiMapsGetAll } from "@/service/api-client/api-maps";
|
||||
import { openInDeviceMaps } from "@/utils/openInDeviceMaps";
|
||||
import { FontAwesome, Ionicons } from "@expo/vector-icons";
|
||||
import { Image } from "expo-image";
|
||||
import { router, useFocusEffect } from "expo-router";
|
||||
import { useCallback, useState } from "react";
|
||||
import { View } from "react-native";
|
||||
import MapView, { Marker } from "react-native-maps";
|
||||
|
||||
const defaultRegion = {
|
||||
latitude: -8.737109,
|
||||
longitude: 115.1756897,
|
||||
latitudeDelta: 0.1,
|
||||
longitudeDelta: 0.1,
|
||||
height: 300,
|
||||
};
|
||||
import MapsView from "@/screens/Maps/MapsView";
|
||||
import MapsView2 from "@/screens/Maps/MapsView2";
|
||||
import { Text, View } from "react-native";
|
||||
|
||||
export interface LocationItem {
|
||||
id: string | number;
|
||||
@@ -37,198 +11,11 @@ export interface LocationItem {
|
||||
}
|
||||
|
||||
export default function Maps() {
|
||||
const [list, setList] = useState<any[] | null>(null);
|
||||
const [loadList, setLoadList] = useState(false);
|
||||
const [openDrawer, setOpenDrawer] = useState(false);
|
||||
const [selected, setSelected] = useState({
|
||||
id: "",
|
||||
bidangBisnis: "",
|
||||
nomorTelepon: "",
|
||||
alamatBisnis: "",
|
||||
namePin: "",
|
||||
imageId: "",
|
||||
portofolioId: "",
|
||||
latitude: 0,
|
||||
longitude: 0,
|
||||
});
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
handlerLoadList();
|
||||
}, [])
|
||||
);
|
||||
|
||||
const handlerLoadList = async () => {
|
||||
try {
|
||||
setLoadList(true);
|
||||
const response = await apiMapsGetAll();
|
||||
|
||||
if (response.success) {
|
||||
setList(response.data);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
} finally {
|
||||
setLoadList(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<ViewWrapper style={{ paddingInline: 0, paddingBlock: 0 }}>
|
||||
{/* <MapCustom height={"100%"} /> */}
|
||||
<View style={{ flex: 1 }}>
|
||||
{loadList ? (
|
||||
<MapView
|
||||
initialRegion={defaultRegion}
|
||||
style={{
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<MapView
|
||||
initialRegion={defaultRegion}
|
||||
style={{
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
}}
|
||||
>
|
||||
{list?.map((item: any, index: number) => {
|
||||
return (
|
||||
<Marker
|
||||
key={item?.id}
|
||||
coordinate={{
|
||||
latitude: item?.latitude,
|
||||
longitude: item?.longitude,
|
||||
}}
|
||||
title={item?.namePin}
|
||||
onPress={() => {
|
||||
setOpenDrawer(true);
|
||||
setSelected({
|
||||
id: item?.id,
|
||||
bidangBisnis:
|
||||
item?.Portofolio?.MasterBidangBisnis?.name,
|
||||
nomorTelepon: item?.Portofolio?.tlpn,
|
||||
alamatBisnis: item?.Portofolio?.alamatKantor,
|
||||
namePin: item?.namePin,
|
||||
imageId: item?.imageId,
|
||||
portofolioId: item?.Portofolio?.id,
|
||||
latitude: item?.latitude,
|
||||
longitude: item?.longitude,
|
||||
});
|
||||
}}
|
||||
// Gunakan gambar kustom jika tersedia
|
||||
>
|
||||
<View style={{}}>
|
||||
<Image
|
||||
source={{
|
||||
uri: API_IMAGE.GET({
|
||||
fileId: item?.Portofolio?.logoId,
|
||||
}),
|
||||
}}
|
||||
style={{
|
||||
width: 30,
|
||||
height: 30,
|
||||
borderRadius: 100,
|
||||
borderWidth: 1,
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
</Marker>
|
||||
);
|
||||
})}
|
||||
</MapView>
|
||||
)}
|
||||
</View>
|
||||
</ViewWrapper>
|
||||
|
||||
<DrawerCustom
|
||||
isVisible={openDrawer}
|
||||
closeDrawer={() => setOpenDrawer(false)}
|
||||
height={"auto"}
|
||||
>
|
||||
<DummyLandscapeImage height={200} imageId={selected.imageId} />
|
||||
<Spacing />
|
||||
<StackCustom gap={"xs"}>
|
||||
<GridTwoView
|
||||
spanLeft={2}
|
||||
spanRight={10}
|
||||
leftIcon={
|
||||
<FontAwesome
|
||||
name="building-o"
|
||||
size={ICON_SIZE_SMALL}
|
||||
color="white"
|
||||
/>
|
||||
}
|
||||
rightIcon={<TextCustom>{selected.namePin}</TextCustom>}
|
||||
/>
|
||||
|
||||
<GridTwoView
|
||||
spanLeft={2}
|
||||
spanRight={10}
|
||||
leftIcon={
|
||||
<Ionicons
|
||||
name="list-outline"
|
||||
size={ICON_SIZE_SMALL}
|
||||
color="white"
|
||||
/>
|
||||
}
|
||||
rightIcon={<TextCustom>{selected.bidangBisnis}</TextCustom>}
|
||||
/>
|
||||
|
||||
<GridTwoView
|
||||
spanLeft={2}
|
||||
spanRight={10}
|
||||
leftIcon={
|
||||
<Ionicons
|
||||
name="call-outline"
|
||||
size={ICON_SIZE_SMALL}
|
||||
color="white"
|
||||
/>
|
||||
}
|
||||
rightIcon={<TextCustom>{selected.nomorTelepon}</TextCustom>}
|
||||
/>
|
||||
<GridTwoView
|
||||
spanLeft={2}
|
||||
spanRight={10}
|
||||
leftIcon={
|
||||
<Ionicons
|
||||
name="location-outline"
|
||||
size={ICON_SIZE_SMALL}
|
||||
color="white"
|
||||
/>
|
||||
}
|
||||
rightIcon={<TextCustom>{selected.alamatBisnis}</TextCustom>}
|
||||
/>
|
||||
|
||||
<Grid>
|
||||
<Grid.Col span={6} style={{ paddingRight: 10 }}>
|
||||
<ButtonCustom
|
||||
onPress={() => {
|
||||
setOpenDrawer(false);
|
||||
router.push(`/portofolio/${selected.portofolioId}`);
|
||||
}}
|
||||
>
|
||||
Detail
|
||||
</ButtonCustom>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={6} style={{ paddingLeft: 10 }}>
|
||||
<ButtonCustom
|
||||
onPress={() => {
|
||||
openInDeviceMaps({
|
||||
latitude: selected.latitude,
|
||||
longitude: selected.longitude,
|
||||
title: selected.namePin,
|
||||
});
|
||||
}}
|
||||
>
|
||||
Buka Maps
|
||||
</ButtonCustom>
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
</StackCustom>
|
||||
</DrawerCustom>
|
||||
<MapsView />
|
||||
{/* <MapsView2 />, */}
|
||||
{/* <View style={{ flex: 1, backgroundColor: "gray" }}><Text style={{ color: "white" }}>Map disabled</Text></View> */}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -31,9 +31,9 @@ import {
|
||||
import pickImage from "@/utils/pickImage";
|
||||
import { Ionicons } from "@expo/vector-icons";
|
||||
import { Image } from "expo-image";
|
||||
import { useLocalSearchParams } from "expo-router";
|
||||
import { useFocusEffect, useLocalSearchParams } from "expo-router";
|
||||
import _ from "lodash";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { Text, TouchableOpacity, View } from "react-native";
|
||||
import PhoneInput, { ICountry } from "react-native-international-phone-number";
|
||||
import { Avatar } from "react-native-paper";
|
||||
@@ -76,7 +76,7 @@ export default function PortofolioCreate() {
|
||||
function handleInputValue(phoneNumber: string) {
|
||||
setInputValue(phoneNumber);
|
||||
const callingCode = selectedCountry?.callingCode.replace(/^\+/, "") || "";
|
||||
const fixNumber = inputValue.replace(/\s+/g, "");
|
||||
let fixNumber = inputValue.replace(/\s+/g, "").replace(/^0+/, "");
|
||||
const realNumber = callingCode + fixNumber;
|
||||
setData({ ...data, tlpn: realNumber });
|
||||
}
|
||||
@@ -85,10 +85,12 @@ export default function PortofolioCreate() {
|
||||
setSelectedCountry(country);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
onLoadMaster();
|
||||
onLoadMasterSubBidangBisnis();
|
||||
}, []);
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
onLoadMaster();
|
||||
onLoadMasterSubBidangBisnis();
|
||||
}, [])
|
||||
);
|
||||
|
||||
const onLoadMaster = async () => {
|
||||
try {
|
||||
|
||||
@@ -244,7 +244,7 @@ export default function PortofolioEdit() {
|
||||
|
||||
const handleSubmitUpdate = async () => {
|
||||
const callingCode = selectedCountry?.callingCode.replace(/^\+/, "") || "";
|
||||
const fixNumber = data.tlpn.replace(/\s+/g, "");
|
||||
let fixNumber = data.tlpn.replace(/\s+/g, "").replace(/^0+/, "");
|
||||
const realNumber = callingCode + fixNumber;
|
||||
|
||||
const newData: IFormData = {
|
||||
|
||||
148
app/(application)/(user)/profile/[id]/blocked-list.tsx
Normal file
148
app/(application)/(user)/profile/[id]/blocked-list.tsx
Normal file
@@ -0,0 +1,148 @@
|
||||
import {
|
||||
AvatarUsernameAndOtherComponent,
|
||||
BadgeCustom,
|
||||
ClickableCustom,
|
||||
Divider,
|
||||
SelectCustom,
|
||||
TextCustom,
|
||||
} from "@/components";
|
||||
import ListEmptyComponent from "@/components/_ShareComponent/ListEmptyComponent";
|
||||
import ListLoaderFooterComponent from "@/components/_ShareComponent/ListLoaderFooterComponent";
|
||||
import ListSkeletonComponent from "@/components/_ShareComponent/ListSkeletonComponent";
|
||||
import NewWrapper from "@/components/_ShareComponent/NewWrapper";
|
||||
import { MainColor } from "@/constants/color-palet";
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
import { usePaginatedApi } from "@/hooks/use-paginated-api";
|
||||
import { apiGetBlocked } from "@/service/api-client/api-blocked";
|
||||
import { apiMasterAppCategory } from "@/service/api-client/api-master";
|
||||
import { router, useFocusEffect } from "expo-router";
|
||||
import _ from "lodash";
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { RefreshControl, View } from "react-native";
|
||||
|
||||
const PAGE_SIZE = 10;
|
||||
export default function ProfileBlockedList() {
|
||||
const { user } = useAuth();
|
||||
const [masterApp, setMasterApp] = useState<any[]>([]);
|
||||
const isInitialMount = useRef(true);
|
||||
|
||||
const {
|
||||
data: listData,
|
||||
loading,
|
||||
refreshing,
|
||||
hasMore,
|
||||
search,
|
||||
setSearch,
|
||||
onRefresh,
|
||||
loadMore,
|
||||
} = usePaginatedApi({
|
||||
fetcher: async (params: { page: number; search?: string }) => {
|
||||
const response = await apiGetBlocked({
|
||||
id: user?.id as any,
|
||||
search: search,
|
||||
page: String(params.page) as any,
|
||||
});
|
||||
|
||||
return response.data;
|
||||
},
|
||||
initialSearch: "",
|
||||
pageSize: PAGE_SIZE,
|
||||
dependencies: [user?.id],
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
fetchMasterApp();
|
||||
}, []);
|
||||
|
||||
// 🔁 Refresh otomatis saat kembali ke halaman ini
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
if (isInitialMount.current) {
|
||||
// Skip saat pertama kali mount
|
||||
isInitialMount.current = false;
|
||||
return;
|
||||
}
|
||||
// Hanya refresh saat kembali dari screen lain
|
||||
onRefresh();
|
||||
}, [onRefresh])
|
||||
);
|
||||
|
||||
const fetchMasterApp = async () => {
|
||||
const response = await apiMasterAppCategory();
|
||||
setMasterApp(response.data);
|
||||
};
|
||||
|
||||
const renderHeader = () => (
|
||||
<SelectCustom
|
||||
placeholder="Pilih Kategori Fitur"
|
||||
data={masterApp.map((item) => ({
|
||||
label: item.name,
|
||||
value: item.id,
|
||||
}))}
|
||||
value={search === "" ? undefined : search}
|
||||
onChange={(value) => {
|
||||
setSearch(value as any);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
const renderItem = ({ item }: { item: any }) => (
|
||||
<>
|
||||
<ClickableCustom
|
||||
onPress={() => {
|
||||
router.push(`/profile/${item.id}/detail-blocked`);
|
||||
}}
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
paddingInline: 8,
|
||||
}}
|
||||
>
|
||||
<AvatarUsernameAndOtherComponent
|
||||
avatarHref={`/profile/${item?.blocked?.Profile?.id}`}
|
||||
avatar={item?.blocked?.Profile?.imageId}
|
||||
name={item?.blocked?.username}
|
||||
rightComponent={
|
||||
<View style={{ flexDirection: "row", gap: 4 }}>
|
||||
<BadgeCustom>
|
||||
<TextCustom size={"small"} bold truncate>
|
||||
{item?.menuFeature?.name}
|
||||
</TextCustom>
|
||||
</BadgeCustom>
|
||||
</View>
|
||||
}
|
||||
/>
|
||||
<Divider color="gray" />
|
||||
</View>
|
||||
</ClickableCustom>
|
||||
</>
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<NewWrapper
|
||||
// headerComponent={renderHeader()}
|
||||
listData={listData}
|
||||
renderItem={renderItem}
|
||||
onEndReached={loadMore}
|
||||
refreshControl={
|
||||
<RefreshControl
|
||||
progressBackgroundColor={MainColor.yellow}
|
||||
refreshing={refreshing}
|
||||
onRefresh={onRefresh}
|
||||
/>
|
||||
}
|
||||
ListFooterComponent={
|
||||
hasMore && !refreshing ? <ListLoaderFooterComponent /> : null
|
||||
}
|
||||
ListEmptyComponent={
|
||||
!loading && _.isEmpty(listData) ? (
|
||||
<ListSkeletonComponent />
|
||||
) : (
|
||||
<ListEmptyComponent />
|
||||
)
|
||||
}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
93
app/(application)/(user)/profile/[id]/detail-blocked.tsx
Normal file
93
app/(application)/(user)/profile/[id]/detail-blocked.tsx
Normal file
@@ -0,0 +1,93 @@
|
||||
import {
|
||||
AlertDefaultSystem,
|
||||
AvatarUsernameAndOtherComponent,
|
||||
BaseBox,
|
||||
BoxButtonOnFooter,
|
||||
BoxWithHeaderSection,
|
||||
ButtonCustom,
|
||||
NewWrapper,
|
||||
StackCustom,
|
||||
TextCustom,
|
||||
} from "@/components";
|
||||
import AvatarAndBackground from "@/screens/Profile/AvatarAndBackground";
|
||||
import {
|
||||
apiGetBlockedById,
|
||||
apiUnblock,
|
||||
} from "@/service/api-client/api-blocked";
|
||||
import { router, useLocalSearchParams } from "expo-router";
|
||||
import _ from "lodash";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
export default function ProfileDetailBlocked() {
|
||||
const { id } = useLocalSearchParams();
|
||||
const [data, setData] = useState<any>(null);
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
|
||||
useEffect(() => {
|
||||
fetchData();
|
||||
}, [id]);
|
||||
|
||||
const fetchData = async () => {
|
||||
const response = await apiGetBlockedById({ id: String(id) });
|
||||
// console.log("[RESPONSE >>]", JSON.stringify(response, null, 2));
|
||||
setData(response.data);
|
||||
};
|
||||
|
||||
const handleSubmit = async () => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
await apiUnblock({ id: String(id) });
|
||||
router.back();
|
||||
} catch (error) {
|
||||
console.log("[ERROR >>]", JSON.stringify(error, null, 2));
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<NewWrapper
|
||||
footerComponent={
|
||||
<BoxButtonOnFooter>
|
||||
<ButtonCustom
|
||||
isLoading={isLoading}
|
||||
onPress={() => {
|
||||
AlertDefaultSystem({
|
||||
title: "Buka Blokir",
|
||||
message: "Apakah anda yakin ingin membuka blokir ini?",
|
||||
textLeft: "Tidak",
|
||||
textRight: "Ya",
|
||||
onPressRight: () => {
|
||||
handleSubmit();
|
||||
},
|
||||
});
|
||||
}}
|
||||
>
|
||||
Buka Blokir
|
||||
</ButtonCustom>
|
||||
</BoxButtonOnFooter>
|
||||
}
|
||||
>
|
||||
<BoxWithHeaderSection>
|
||||
<StackCustom>
|
||||
<AvatarUsernameAndOtherComponent
|
||||
avatarHref={`/profile/${data?.blocked?.Profile?.id}`}
|
||||
avatar={data?.blocked?.Profile?.imageId}
|
||||
name={data?.blocked?.username}
|
||||
/>
|
||||
|
||||
<TextCustom align="center">
|
||||
Jika anda membuka blokir ini maka semua postingan terkait user ini
|
||||
akan muncul kembali di beranda
|
||||
<TextCustom bold color="red">
|
||||
{" "}
|
||||
{_.upperCase(data?.menuFeature?.name)}
|
||||
</TextCustom>
|
||||
</TextCustom>
|
||||
</StackCustom>
|
||||
</BoxWithHeaderSection>
|
||||
</NewWrapper>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -64,14 +64,18 @@ export default function Profile() {
|
||||
};
|
||||
|
||||
const onLoadPortofolio = async (id: string) => {
|
||||
const response = await apiGetPortofolio({ id: id });
|
||||
const lastTwoByDate = response.data
|
||||
.sort(
|
||||
(a: any, b: any) =>
|
||||
new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()
|
||||
) // urut desc
|
||||
.slice(0, 2);
|
||||
setListPortofolio(lastTwoByDate);
|
||||
try {
|
||||
const response = await apiGetPortofolio({ id: id });
|
||||
const lastTwoByDate = response.data
|
||||
.sort(
|
||||
(a: any, b: any) =>
|
||||
new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()
|
||||
) // urut desc
|
||||
.slice(0, 2);
|
||||
setListPortofolio(lastTwoByDate);
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -33,6 +33,16 @@ export default function ProfileLayout() {
|
||||
name="create"
|
||||
options={{ title: "Buat Profile", headerBackVisible: false }}
|
||||
/>
|
||||
|
||||
<Stack.Screen
|
||||
name="[id]/blocked-list"
|
||||
options={{ title: "Blocked List", headerLeft: () => <BackButton /> }}
|
||||
/>
|
||||
|
||||
<Stack.Screen
|
||||
name="[id]/detail-blocked"
|
||||
options={{ title: "Detail Blokir", headerLeft: () => <BackButton /> }}
|
||||
/>
|
||||
</Stack>
|
||||
</>
|
||||
);
|
||||
|
||||
77
app/(application)/(user)/test-notifications.tsx
Normal file
77
app/(application)/(user)/test-notifications.tsx
Normal file
@@ -0,0 +1,77 @@
|
||||
import {
|
||||
ButtonCustom,
|
||||
NewWrapper,
|
||||
StackCustom,
|
||||
TextInputCustom,
|
||||
} from "@/components";
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
import { apiGetAllTokenDevice } from "@/service/api-device-token";
|
||||
import { apiNotificationsSend } from "@/service/api-notifications";
|
||||
import { useEffect, useState } from "react";
|
||||
import Toast from "react-native-toast-message";
|
||||
|
||||
export default function TestNotification() {
|
||||
const { user } = useAuth();
|
||||
const [data, setData] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
// fecthData();
|
||||
}, []);
|
||||
|
||||
const fecthData = async () => {
|
||||
const response = await apiGetAllTokenDevice();
|
||||
console.log(
|
||||
"[RES GET ALL TOKEN DEVICE]",
|
||||
JSON.stringify(response.data, null, 2)
|
||||
);
|
||||
};
|
||||
|
||||
const handleSubmit = async () => {
|
||||
console.log("[Data Dikirim]", data);
|
||||
const response = await apiNotificationsSend({
|
||||
data: {
|
||||
fcmToken:
|
||||
"cVmHm-3P4E-1vjt6AA9kSF:APA91bHTkHjGTLxrFsb6Le6bZmzboZhwMGYXU4p0FP9yEeXixLDXNKS4F5vLuZV3sRgSnjjQsPpLOgstVLHJB8VJTObctKLdN-CxAp4dnP7Jbc_mH53jWvs",
|
||||
title: "Test dari Backend (App Router)!",
|
||||
body: data,
|
||||
userLoginId: user?.id || "",
|
||||
},
|
||||
});
|
||||
|
||||
if (response.success) {
|
||||
console.log("[RES SEND NOTIF]", JSON.stringify(response, null, 2));
|
||||
Toast.show({
|
||||
type: "success",
|
||||
text1: "Notifikasi berhasil dikirim",
|
||||
});
|
||||
} else {
|
||||
Toast.show({
|
||||
type: "error",
|
||||
text1: "Gagal mengirim notifikasi",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<NewWrapper>
|
||||
<StackCustom>
|
||||
<TextInputCustom
|
||||
required
|
||||
label="Nama"
|
||||
placeholder="Masukkan nama"
|
||||
value={data}
|
||||
onChangeText={(text) => setData(text)}
|
||||
/>
|
||||
<ButtonCustom
|
||||
onPress={() => {
|
||||
handleSubmit();
|
||||
}}
|
||||
>
|
||||
Kirim
|
||||
</ButtonCustom>
|
||||
</StackCustom>
|
||||
</NewWrapper>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
TextCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
import Voting_BoxPublishSection from "@/screens/Voting/BoxPublishSection";
|
||||
import { apiVotingGetAll } from "@/service/api-client/api-voting";
|
||||
import { router, useFocusEffect } from "expo-router";
|
||||
@@ -13,6 +14,7 @@ import _ from "lodash";
|
||||
import { useCallback, useState } from "react";
|
||||
|
||||
export default function VotingBeranda() {
|
||||
const { user } = useAuth();
|
||||
const [listData, setListData] = useState<any>([]);
|
||||
const [loadingGetData, setLoadingGetData] = useState(false);
|
||||
const [search, setSearch] = useState("");
|
||||
@@ -29,6 +31,7 @@ export default function VotingBeranda() {
|
||||
const response = await apiVotingGetAll({
|
||||
search,
|
||||
category: "beranda",
|
||||
userLoginId: user?.id,
|
||||
});
|
||||
if (response.success) {
|
||||
setListData(response.data);
|
||||
|
||||
@@ -134,7 +134,7 @@ export default function VotingDetailStatus() {
|
||||
|
||||
{data &&
|
||||
data?.catatan &&
|
||||
(status === "draft" || status === "rejected") && (
|
||||
(status === "draft" || status === "reject") && (
|
||||
<ReportBox text={data?.catatan} />
|
||||
)}
|
||||
|
||||
|
||||
@@ -110,7 +110,7 @@ export default function VotingCreate() {
|
||||
<StackCustom gap={"xs"}>
|
||||
<TextInputCustom
|
||||
label="Judul Voting"
|
||||
placeholder="MasukanJudul Voting"
|
||||
placeholder="Masukan Judul Voting"
|
||||
required
|
||||
value={data.title}
|
||||
onChangeText={(value: any) => setData({ ...data, title: value })}
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
ButtonCenteredOnly,
|
||||
ButtonCustom,
|
||||
InformationBox,
|
||||
NewWrapper,
|
||||
StackCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
@@ -12,6 +13,7 @@ import { useAuth } from "@/hooks/use-auth";
|
||||
import { apiUser } from "@/service/api-client/api-user";
|
||||
import { Ionicons } from "@expo/vector-icons";
|
||||
import { router } from "expo-router";
|
||||
import { RefreshControl } from "react-native";
|
||||
import Toast from "react-native-toast-message";
|
||||
|
||||
export default function WaitingRoom() {
|
||||
@@ -33,7 +35,7 @@ export default function WaitingRoom() {
|
||||
} else {
|
||||
Toast.show({
|
||||
type: "success",
|
||||
text1: "Akun anda telah aktif", // text2: "Anda berhasil login",
|
||||
text1: "Selamat ! Akun anda telah aktif", // text2: "Anda berhasil login",
|
||||
});
|
||||
router.replace(`/(application)/(user)/profile/create`);
|
||||
}
|
||||
@@ -82,10 +84,18 @@ export default function WaitingRoom() {
|
||||
|
||||
return (
|
||||
<>
|
||||
<ViewWrapper footerComponent={logoutButton()}>
|
||||
<NewWrapper
|
||||
footerComponent={logoutButton()}
|
||||
refreshControl={
|
||||
<RefreshControl refreshing={isLoading} onRefresh={handleCheck} />
|
||||
}
|
||||
>
|
||||
<StackCustom>
|
||||
<InformationBox text="Permohonan akses Anda sedang dalam proses verifikasi oleh admin. Harap tunggu, Anda akan menerima pemberitahuan melalui Whatsapp setelah disetujui." />
|
||||
<ButtonCenteredOnly
|
||||
<InformationBox
|
||||
text="Akun Anda sedang menunggu aktivasi.
|
||||
Silakan tunggu beberapa saat. Untuk memperbarui status, tarik layar ke bawah."
|
||||
/>
|
||||
{/* <ButtonCenteredOnly
|
||||
isLoading={isLoading}
|
||||
onPress={() => {
|
||||
handleCheck();
|
||||
@@ -93,9 +103,9 @@ export default function WaitingRoom() {
|
||||
icon="refresh-ccw"
|
||||
>
|
||||
Check
|
||||
</ButtonCenteredOnly>
|
||||
</ButtonCenteredOnly> */}
|
||||
</StackCustom>
|
||||
</ViewWrapper>
|
||||
</NewWrapper>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,14 +1,26 @@
|
||||
import { BackButton } from "@/components";
|
||||
import NotificationInitializer from "@/components/Notification/NotificationInitializer";
|
||||
import { NotificationProvider } from "@/hooks/use-notification-store";
|
||||
import { HeaderStyles } from "@/styles/header-styles";
|
||||
import { Stack } from "expo-router";
|
||||
|
||||
export default function ApplicationLayout() {
|
||||
return (
|
||||
<>
|
||||
<NotificationProvider>
|
||||
<NotificationInitializer />
|
||||
<ApplicationStack />
|
||||
</NotificationProvider>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function ApplicationStack() {
|
||||
return (
|
||||
<>
|
||||
<Stack screenOptions={HeaderStyles}>
|
||||
<Stack.Screen name="(user)" options={{ headerShown: false }} />
|
||||
<Stack.Screen name="admin" options={{ headerShown: false }} />
|
||||
|
||||
|
||||
{/* Take Picture */}
|
||||
<Stack.Screen
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
BoxButtonOnFooter,
|
||||
ButtonCustom,
|
||||
StackCustom,
|
||||
TextCustom,
|
||||
TextInputCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButtonAntTitle";
|
||||
import { MainColor } from "@/constants/color-palet";
|
||||
import {
|
||||
apiAdminMasterBusinessFieldById,
|
||||
apiAdminMasterBusinessFieldUpdate,
|
||||
} from "@/service/api-admin/api-master-admin";
|
||||
import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
|
||||
import { useCallback, useState } from "react";
|
||||
import { Switch } from "react-native-paper";
|
||||
import Toast from "react-native-toast-message";
|
||||
|
||||
export default function AdminAppInformation_BusinessFieldDetail() {
|
||||
const { id } = useLocalSearchParams();
|
||||
const [data, setData] = useState<any | null>(null);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
onLoadDetail();
|
||||
}, [id])
|
||||
);
|
||||
|
||||
const onLoadDetail = async () => {
|
||||
try {
|
||||
const response = await apiAdminMasterBusinessFieldById({
|
||||
id: id as string,
|
||||
category: "bidang"
|
||||
});
|
||||
|
||||
setData(response.data);
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
setData(null);
|
||||
}
|
||||
};
|
||||
|
||||
const handlerSubmit = async () => {
|
||||
if (!data.name) {
|
||||
Toast.show({
|
||||
type: "error",
|
||||
text1: "Lengkapi Data",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
setIsLoading(true);
|
||||
const response = await apiAdminMasterBusinessFieldUpdate({
|
||||
id: id as string,
|
||||
data: data,
|
||||
category: "bidang",
|
||||
});
|
||||
|
||||
if (!response.success) {
|
||||
Toast.show({
|
||||
type: "error",
|
||||
text1: "Gagal update data",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
Toast.show({
|
||||
type: "success",
|
||||
text1: "Data berhasil di update",
|
||||
});
|
||||
router.back();
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const buttonSubmit = (
|
||||
<BoxButtonOnFooter>
|
||||
<ButtonCustom
|
||||
disabled={!data?.name}
|
||||
isLoading={isLoading}
|
||||
onPress={() => handlerSubmit()}
|
||||
>
|
||||
Update
|
||||
</ButtonCustom>
|
||||
</BoxButtonOnFooter>
|
||||
);
|
||||
return (
|
||||
<>
|
||||
<ViewWrapper footerComponent={buttonSubmit}>
|
||||
<StackCustom>
|
||||
<AdminBackButtonAntTitle title="Update Bidang Bisnis" />
|
||||
|
||||
<TextInputCustom
|
||||
label="Nama Bidang Bisnis"
|
||||
placeholder="Masukan Nama Bidang Bisnis"
|
||||
required
|
||||
value={data?.name}
|
||||
onChangeText={(value) => setData({ ...data, name: value })}
|
||||
/>
|
||||
|
||||
<StackCustom
|
||||
gap={"sm"}
|
||||
style={{
|
||||
alignContent: "flex-start",
|
||||
}}
|
||||
>
|
||||
<TextCustom>Status</TextCustom>
|
||||
|
||||
<Switch
|
||||
style={{
|
||||
alignSelf: "flex-start",
|
||||
}}
|
||||
color={MainColor.yellow}
|
||||
value={data?.active}
|
||||
onValueChange={(value) => setData({ ...data, active: value })}
|
||||
/>
|
||||
</StackCustom>
|
||||
</StackCustom>
|
||||
</ViewWrapper>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,22 +1,22 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
BoxButtonOnFooter,
|
||||
ButtonCustom,
|
||||
ActionIcon,
|
||||
BaseBox,
|
||||
CenterCustom,
|
||||
LoaderCustom,
|
||||
Spacing,
|
||||
StackCustom,
|
||||
TextCustom,
|
||||
TextInputCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import { IconEdit } from "@/components/_Icon";
|
||||
import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButtonAntTitle";
|
||||
import { GridSpan_NewComponent } from "@/components/_ShareComponent/GridSpan_NewComponent";
|
||||
import { MainColor } from "@/constants/color-palet";
|
||||
import {
|
||||
apiAdminMasterBusinessFieldById,
|
||||
apiAdminMasterBusinessFieldUpdate,
|
||||
} from "@/service/api-admin/api-master-admin";
|
||||
import { apiAdminMasterBusinessFieldById } from "@/service/api-admin/api-master-admin";
|
||||
import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
|
||||
import { useCallback, useState } from "react";
|
||||
import { Switch } from "react-native-paper";
|
||||
import Toast from "react-native-toast-message";
|
||||
import { Divider } from "react-native-paper";
|
||||
|
||||
export default function AdminAppInformation_BusinessFieldDetail() {
|
||||
const { id } = useLocalSearchParams();
|
||||
@@ -33,8 +33,11 @@ export default function AdminAppInformation_BusinessFieldDetail() {
|
||||
try {
|
||||
const response = await apiAdminMasterBusinessFieldById({
|
||||
id: id as string,
|
||||
category: "all",
|
||||
});
|
||||
|
||||
console.log("Response >>", JSON.stringify(response, null, 2));
|
||||
|
||||
setData(response.data);
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
@@ -42,73 +45,89 @@ export default function AdminAppInformation_BusinessFieldDetail() {
|
||||
}
|
||||
};
|
||||
|
||||
const handlerSubmit = async () => {
|
||||
if (!data.name) {
|
||||
Toast.show({
|
||||
type: "error",
|
||||
text1: "Lengkapi Data",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
setIsLoading(true);
|
||||
const response = await apiAdminMasterBusinessFieldUpdate({
|
||||
id: id as string,
|
||||
data: data,
|
||||
});
|
||||
|
||||
if (!response.success) {
|
||||
Toast.show({
|
||||
type: "error",
|
||||
text1: "Gagal update data",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
Toast.show({
|
||||
type: "success",
|
||||
text1: "Data berhasil di update",
|
||||
});
|
||||
router.back();
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const buttonSubmit = (
|
||||
<BoxButtonOnFooter>
|
||||
<ButtonCustom
|
||||
disabled={!data?.name}
|
||||
isLoading={isLoading}
|
||||
onPress={() => handlerSubmit()}
|
||||
>
|
||||
Update
|
||||
</ButtonCustom>
|
||||
</BoxButtonOnFooter>
|
||||
);
|
||||
return (
|
||||
<>
|
||||
<ViewWrapper footerComponent={buttonSubmit}>
|
||||
<ViewWrapper>
|
||||
<StackCustom>
|
||||
<AdminBackButtonAntTitle title="Update Bidang Bisnis" />
|
||||
<AdminBackButtonAntTitle title="Detail Bidang & Sub Bidang" />
|
||||
|
||||
<TextInputCustom
|
||||
label="Nama Bidang Bisnis"
|
||||
placeholder="Masukan Nama Bidang Bisnis"
|
||||
required
|
||||
value={data?.name}
|
||||
onChangeText={(value) => setData({ ...data, name: value })}
|
||||
/>
|
||||
{!data ? (
|
||||
<LoaderCustom />
|
||||
) : (
|
||||
<StackCustom gap={"xs"}>
|
||||
<TextCustom bold>Nama Bidang</TextCustom>
|
||||
<Spacing height={5} />
|
||||
<BaseBox>
|
||||
<StackCustom gap={"xs"}>
|
||||
<TextCustom bold>
|
||||
Status: {data?.bidang?.active ? "Aktif" : "Tidak Aktif"}
|
||||
</TextCustom>
|
||||
<GridSpan_NewComponent
|
||||
span1={10}
|
||||
span2={2}
|
||||
text1={
|
||||
<TextCustom bold size={"large"}>
|
||||
{data?.bidang?.name}
|
||||
</TextCustom>
|
||||
}
|
||||
text2={
|
||||
<CenterCustom>
|
||||
<ActionIcon
|
||||
icon={<IconEdit size={16} color={MainColor.black} />}
|
||||
onPress={() =>
|
||||
router.push(
|
||||
`/admin/app-information/business-field/${id}/bidang-update`
|
||||
)
|
||||
}
|
||||
/>
|
||||
</CenterCustom>
|
||||
}
|
||||
/>
|
||||
</StackCustom>
|
||||
</BaseBox>
|
||||
{/* <Divider /> */}
|
||||
<Spacing height={5} />
|
||||
|
||||
<TextCustom>Status Aktivasi</TextCustom>
|
||||
<Switch
|
||||
color={MainColor.yellow}
|
||||
value={data?.active}
|
||||
onValueChange={(value) => setData({ ...data, active: value })}
|
||||
/>
|
||||
<TextCustom bold>Sub Bidang Bisnis</TextCustom>
|
||||
<Spacing height={5} />
|
||||
|
||||
{data?.subBidang?.map((item: any, index: number) => (
|
||||
<BaseBox key={index}>
|
||||
<StackCustom gap={0}>
|
||||
<TextCustom bold>
|
||||
Status: {item?.isActive ? "Aktif" : "Tidak Aktif"}
|
||||
</TextCustom>
|
||||
|
||||
<GridSpan_NewComponent
|
||||
span1={10}
|
||||
span2={2}
|
||||
text1={
|
||||
<TextCustom bold size={"large"}>
|
||||
{item.name}
|
||||
</TextCustom>
|
||||
}
|
||||
text2={
|
||||
<CenterCustom>
|
||||
<ActionIcon
|
||||
icon={
|
||||
<IconEdit size={16} color={MainColor.black} />
|
||||
}
|
||||
onPress={() =>
|
||||
router.push(
|
||||
`/admin/app-information/business-field/${item?.id}/sub-bidang-update`
|
||||
)
|
||||
}
|
||||
/>
|
||||
</CenterCustom>
|
||||
}
|
||||
/>
|
||||
</StackCustom>
|
||||
</BaseBox>
|
||||
))}
|
||||
</StackCustom>
|
||||
)}
|
||||
|
||||
{/* <TextCustom>{JSON.stringify(data, null, 2)}</TextCustom> */}
|
||||
</StackCustom>
|
||||
</ViewWrapper>
|
||||
</>
|
||||
|
||||
@@ -0,0 +1,135 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
BoxButtonOnFooter,
|
||||
ButtonCustom,
|
||||
StackCustom,
|
||||
TextCustom,
|
||||
TextInputCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButtonAntTitle";
|
||||
import { MainColor } from "@/constants/color-palet";
|
||||
import {
|
||||
apiAdminMasterBusinessFieldById,
|
||||
apiAdminMasterBusinessFieldUpdate,
|
||||
} from "@/service/api-admin/api-master-admin";
|
||||
import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
|
||||
import { useCallback, useState } from "react";
|
||||
import { Switch } from "react-native-paper";
|
||||
import Toast from "react-native-toast-message";
|
||||
|
||||
export default function AdminAppInformation_BusinessFieldDetail() {
|
||||
const { id } = useLocalSearchParams();
|
||||
const [data, setData] = useState<any | null>(null);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
onLoadDetail();
|
||||
}, [id])
|
||||
);
|
||||
|
||||
const onLoadDetail = async () => {
|
||||
try {
|
||||
const response = await apiAdminMasterBusinessFieldById({
|
||||
id: id as string,
|
||||
category: "sub-bidang",
|
||||
subBidangId: id as string,
|
||||
});
|
||||
|
||||
console.log("Response >>", JSON.stringify(response, null, 2));
|
||||
|
||||
|
||||
|
||||
|
||||
setData(response.data);
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
setData(null);
|
||||
}
|
||||
};
|
||||
|
||||
const handlerSubmit = async () => {
|
||||
if (!data.name) {
|
||||
Toast.show({
|
||||
type: "error",
|
||||
text1: "Lengkapi Data",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
setIsLoading(true);
|
||||
const response = await apiAdminMasterBusinessFieldUpdate({
|
||||
id: id as string,
|
||||
data: data,
|
||||
category: "sub-bidang",
|
||||
});
|
||||
|
||||
if (!response.success) {
|
||||
Toast.show({
|
||||
type: "error",
|
||||
text1: "Gagal update data",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
Toast.show({
|
||||
type: "success",
|
||||
text1: "Data berhasil di update",
|
||||
});
|
||||
router.back();
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const buttonSubmit = (
|
||||
<BoxButtonOnFooter>
|
||||
<ButtonCustom
|
||||
disabled={!data?.name}
|
||||
isLoading={isLoading}
|
||||
onPress={() => handlerSubmit()}
|
||||
>
|
||||
Update
|
||||
</ButtonCustom>
|
||||
</BoxButtonOnFooter>
|
||||
);
|
||||
return (
|
||||
<>
|
||||
<ViewWrapper footerComponent={buttonSubmit}>
|
||||
<StackCustom>
|
||||
<AdminBackButtonAntTitle title="Update Bidang Bisnis" />
|
||||
|
||||
<TextInputCustom
|
||||
label="Nama Bidang Bisnis"
|
||||
placeholder="Masukan Nama Bidang Bisnis"
|
||||
required
|
||||
value={data?.name}
|
||||
onChangeText={(value) => setData({ ...data, name: value })}
|
||||
/>
|
||||
|
||||
<StackCustom
|
||||
gap={"sm"}
|
||||
style={{
|
||||
alignContent: "flex-start",
|
||||
}}
|
||||
>
|
||||
<TextCustom>Status</TextCustom>
|
||||
|
||||
<Switch
|
||||
style={{
|
||||
alignSelf: "flex-start",
|
||||
}}
|
||||
color={MainColor.yellow}
|
||||
value={data?.isActive}
|
||||
onValueChange={(value) => setData({ ...data, isActive: value })}
|
||||
/>
|
||||
</StackCustom>
|
||||
</StackCustom>
|
||||
</ViewWrapper>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,40 +1,81 @@
|
||||
import {
|
||||
BoxButtonOnFooter,
|
||||
ButtonCustom,
|
||||
StackCustom,
|
||||
TextInputCustom,
|
||||
ViewWrapper,
|
||||
ActionIcon,
|
||||
BoxButtonOnFooter,
|
||||
ButtonCustom,
|
||||
CenterCustom,
|
||||
Grid,
|
||||
Spacing,
|
||||
StackCustom,
|
||||
TextInputCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButtonAntTitle";
|
||||
import { MainColor } from "@/constants/color-palet";
|
||||
import { ICON_SIZE_XLARGE } from "@/constants/constans-value";
|
||||
import { apiAdminMasterBusinessFieldCreate } from "@/service/api-admin/api-master-admin";
|
||||
import { Ionicons } from "@expo/vector-icons";
|
||||
import { router } from "expo-router";
|
||||
import _ from "lodash";
|
||||
import { useState } from "react";
|
||||
import { View } from "react-native";
|
||||
import { Divider } from "react-native-paper";
|
||||
import Toast from "react-native-toast-message";
|
||||
|
||||
export default function AdminAppInformation_BusinessFieldCreate() {
|
||||
const [data, setData] = useState<any>({
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [bidang, setBidang] = useState<any>({
|
||||
name: "",
|
||||
});
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [subBidang, setSubBidang] = useState<any[]>([
|
||||
{
|
||||
name: "",
|
||||
},
|
||||
]);
|
||||
|
||||
const handlerSubmit = async () => {
|
||||
if (!data.name) {
|
||||
if (!bidang.name) {
|
||||
Toast.show({
|
||||
type: "error",
|
||||
text1: "Lengkapi Data",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (subBidang[0].name === "") {
|
||||
Toast.show({
|
||||
type: "error",
|
||||
text1: "Lengkapi Sub Bidang",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
setIsLoading(true);
|
||||
const response = await apiAdminMasterBusinessFieldCreate({ data: data });
|
||||
|
||||
const newData = {
|
||||
bidang: bidang,
|
||||
subBidang: subBidang,
|
||||
};
|
||||
|
||||
console.log("[DATA]", newData);
|
||||
|
||||
const response = await apiAdminMasterBusinessFieldCreate({
|
||||
data: newData,
|
||||
});
|
||||
|
||||
console.log("[RESPONSE]", response);
|
||||
|
||||
if (response.success) {
|
||||
Toast.show({
|
||||
type: "success",
|
||||
text1: "Data berhasil di tambah",
|
||||
});
|
||||
router.back();
|
||||
// router.back();
|
||||
} else {
|
||||
Toast.show({
|
||||
type: "error",
|
||||
text1: "Gagal tambah data",
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
@@ -50,6 +91,7 @@ export default function AdminAppInformation_BusinessFieldCreate() {
|
||||
const buttonSubmit = (
|
||||
<BoxButtonOnFooter>
|
||||
<ButtonCustom
|
||||
disabled={subBidang[0].name === ""}
|
||||
onPress={() => handlerSubmit()}
|
||||
isLoading={isLoading}
|
||||
>
|
||||
@@ -60,16 +102,70 @@ export default function AdminAppInformation_BusinessFieldCreate() {
|
||||
return (
|
||||
<>
|
||||
<ViewWrapper footerComponent={buttonSubmit}>
|
||||
<StackCustom>
|
||||
<StackCustom gap={"xs"}>
|
||||
<AdminBackButtonAntTitle title="Tambah Bidang Bisnis" />
|
||||
|
||||
<TextInputCustom
|
||||
label="Nama Bidang Bisnis"
|
||||
placeholder="Masukan Nama Bidang Bisnis"
|
||||
required
|
||||
value={data.name}
|
||||
onChangeText={(value) => setData({ ...data, name: value })}
|
||||
value={bidang.name}
|
||||
onChangeText={(value) => setBidang({ ...bidang, name: value })}
|
||||
/>
|
||||
|
||||
<Divider />
|
||||
<Spacing height={5} />
|
||||
|
||||
{subBidang.map((item, index) => (
|
||||
<TextInputCustom
|
||||
key={index}
|
||||
label="Nama Sub Bidang"
|
||||
placeholder="Masukan Nama Sub Bidang"
|
||||
required
|
||||
value={item.name}
|
||||
onChangeText={(value) => {
|
||||
const list = _.clone(subBidang);
|
||||
list[index].name = value;
|
||||
setSubBidang(list);
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
|
||||
<CenterCustom>
|
||||
<View
|
||||
style={{ flexDirection: "row", alignItems: "center", gap: 10 }}
|
||||
>
|
||||
<ActionIcon
|
||||
onPress={() => {
|
||||
setSubBidang([...subBidang, { name: "" }]);
|
||||
}}
|
||||
icon={
|
||||
<Ionicons
|
||||
name="add-circle-outline"
|
||||
size={ICON_SIZE_XLARGE}
|
||||
color={MainColor.black}
|
||||
/>
|
||||
}
|
||||
size="xl"
|
||||
/>
|
||||
<ActionIcon
|
||||
disabled={subBidang.length <= 1}
|
||||
onPress={() => {
|
||||
const list = _.clone(subBidang);
|
||||
list.pop();
|
||||
setSubBidang(list);
|
||||
}}
|
||||
icon={
|
||||
<Ionicons
|
||||
name="remove-circle-outline"
|
||||
size={ICON_SIZE_XLARGE}
|
||||
color={MainColor.black}
|
||||
/>
|
||||
}
|
||||
size="xl"
|
||||
/>
|
||||
</View>
|
||||
</CenterCustom>
|
||||
</StackCustom>
|
||||
</ViewWrapper>
|
||||
</>
|
||||
|
||||
@@ -75,7 +75,7 @@ const listPage = [
|
||||
},
|
||||
{
|
||||
id: "2",
|
||||
label: "Bidang Bisnis",
|
||||
label: "Bidang & Sub Bidang",
|
||||
value: "business",
|
||||
},
|
||||
{
|
||||
|
||||
@@ -2,11 +2,13 @@
|
||||
import {
|
||||
BaseBox,
|
||||
Grid,
|
||||
Spacing,
|
||||
StackCustom,
|
||||
TextCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButtonAntTitle";
|
||||
import { GridSpan_4_8 } from "@/components/_ShareComponent/GridSpan_4_8";
|
||||
import { apiAdminCollaborationGetById } from "@/service/api-admin/api-admin-collaboration";
|
||||
import { useFocusEffect, useLocalSearchParams } from "expo-router";
|
||||
import { useCallback, useState } from "react";
|
||||
@@ -28,6 +30,8 @@ export default function AdminCollaborationGroup() {
|
||||
category: "group",
|
||||
});
|
||||
|
||||
console.log("[DATA]", JSON.stringify(response.data, null, 2));
|
||||
|
||||
if (response.success) {
|
||||
setData(response.data);
|
||||
}
|
||||
@@ -59,38 +63,33 @@ export default function AdminCollaborationGroup() {
|
||||
))}
|
||||
</StackCustom>
|
||||
</BaseBox>
|
||||
|
||||
<TextCustom bold>Anggota</TextCustom>
|
||||
<Spacing height={5}/>
|
||||
<BaseBox>
|
||||
<StackCustom>
|
||||
<TextCustom align="center">Anggota</TextCustom>
|
||||
|
||||
<Grid>
|
||||
<Grid.Col span={6} style={{ justifyContent: "center", paddingRight: 10 }}>
|
||||
<TextCustom bold>Nomor</TextCustom>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={6} style={{ justifyContent: "center" }}>
|
||||
<TextCustom bold>Username</TextCustom>
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
|
||||
{data?.ProjectCollaboration_AnggotaRoomChat?.map(
|
||||
(item: any, index: number) => (
|
||||
<StackCustom key={index} gap={0}>
|
||||
<Grid>
|
||||
<Grid.Col
|
||||
span={4}
|
||||
style={{ justifyContent: "center", paddingRight: 10 }}
|
||||
>
|
||||
<TextCustom bold>Nama</TextCustom>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={8} style={{ justifyContent: "center" }}>
|
||||
<TextCustom>
|
||||
{item?.User?.Profile?.name || "-"}
|
||||
</TextCustom>
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
<Grid>
|
||||
<Grid.Col
|
||||
span={4}
|
||||
style={{ justifyContent: "center", paddingRight: 10 }}
|
||||
>
|
||||
<TextCustom bold>Username</TextCustom>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={8} style={{ justifyContent: "center" }}>
|
||||
<TextCustom>{item?.User?.username || "-"}</TextCustom>
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
</StackCustom>
|
||||
<Grid key={index}>
|
||||
<Grid.Col span={6} style={{ justifyContent: "center", paddingRight: 10 }}>
|
||||
<TextCustom bold truncate>+{item?.User?.nomor || "-"}</TextCustom>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={6} style={{ justifyContent: "center" }}>
|
||||
<TextCustom bold>
|
||||
{item?.User?.username || "-"}
|
||||
</TextCustom>
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
)
|
||||
)}
|
||||
</StackCustom>
|
||||
|
||||
@@ -1,17 +1,14 @@
|
||||
import {
|
||||
ActionIcon,
|
||||
ClickableCustom,
|
||||
LoaderCustom,
|
||||
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 { GridSpan_NewComponent } from "@/components/_ShareComponent/GridSpan_NewComponent";
|
||||
import { apiAdminCollaboration } from "@/service/api-admin/api-admin-collaboration";
|
||||
import { Octicons } from "@expo/vector-icons";
|
||||
import { router, useFocusEffect } from "expo-router";
|
||||
import _ from "lodash";
|
||||
import { useCallback, useState } from "react";
|
||||
@@ -34,7 +31,7 @@ export default function AdminCollaborationGroup() {
|
||||
const response = await apiAdminCollaboration({
|
||||
category: "group",
|
||||
});
|
||||
|
||||
|
||||
if (response.success) {
|
||||
setList(response.data);
|
||||
}
|
||||
@@ -51,10 +48,19 @@ export default function AdminCollaborationGroup() {
|
||||
<StackCustom>
|
||||
<AdminComp_BoxTitle title="Group" />
|
||||
<>
|
||||
<AdminTitleTable
|
||||
title1="Aksi"
|
||||
title2="Jumlah peserta"
|
||||
title3="Nama group"
|
||||
<GridSpan_NewComponent
|
||||
span1={6}
|
||||
span2={6}
|
||||
text1={
|
||||
<TextCustom bold truncate align="center">
|
||||
Jumlah Anggota
|
||||
</TextCustom>
|
||||
}
|
||||
text2={
|
||||
<TextCustom bold truncate>
|
||||
Nama Group
|
||||
</TextCustom>
|
||||
}
|
||||
/>
|
||||
<Divider />
|
||||
|
||||
@@ -67,31 +73,27 @@ export default function AdminCollaborationGroup() {
|
||||
) : (
|
||||
list?.map((item: any, index: number) => (
|
||||
<View key={index}>
|
||||
<AdminTableValue
|
||||
value1={
|
||||
<ActionIcon
|
||||
icon={
|
||||
<Octicons
|
||||
name="eye"
|
||||
size={ICON_SIZE_BUTTON}
|
||||
color="black"
|
||||
/>
|
||||
}
|
||||
onPress={() => {
|
||||
router.push(`/admin/collaboration/${item.id}/group`);
|
||||
}}
|
||||
/>
|
||||
}
|
||||
value2={
|
||||
<TextCustom truncate={1}>
|
||||
{item?.ProjectCollaboration_AnggotaRoomChat?.length ||
|
||||
"-"}
|
||||
</TextCustom>
|
||||
}
|
||||
value3={
|
||||
<TextCustom truncate={2}>{item?.name || "-"}</TextCustom>
|
||||
}
|
||||
/>
|
||||
<ClickableCustom
|
||||
onPress={() => {
|
||||
router.push(`/admin/collaboration/${item.id}/group`);
|
||||
}}
|
||||
>
|
||||
<GridSpan_NewComponent
|
||||
span1={6}
|
||||
span2={6}
|
||||
text1={
|
||||
<TextCustom truncate={1} align="center">
|
||||
{item?.ProjectCollaboration_AnggotaRoomChat?.length ||
|
||||
"-"}
|
||||
</TextCustom>
|
||||
}
|
||||
text2={
|
||||
<TextCustom truncate={2}>
|
||||
{item?.name || "-"}
|
||||
</TextCustom>
|
||||
}
|
||||
/>
|
||||
</ClickableCustom>
|
||||
</View>
|
||||
))
|
||||
)}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import {
|
||||
ActionIcon,
|
||||
ClickableCustom,
|
||||
LoaderCustom,
|
||||
Spacing,
|
||||
StackCustom,
|
||||
TextCustom,
|
||||
ViewWrapper,
|
||||
@@ -9,6 +11,7 @@ import AdminComp_BoxTitle from "@/components/_ShareComponent/Admin/BoxTitlePage"
|
||||
import AdminTitleTable from "@/components/_ShareComponent/Admin/TableTitle";
|
||||
import AdminTableValue from "@/components/_ShareComponent/Admin/TableValue";
|
||||
import AdminTitlePage from "@/components/_ShareComponent/Admin/TitlePage";
|
||||
import { GridSpan_NewComponent } from "@/components/_ShareComponent/GridSpan_NewComponent";
|
||||
import { ICON_SIZE_BUTTON } from "@/constants/constans-value";
|
||||
import { apiAdminCollaboration } from "@/service/api-admin/api-admin-collaboration";
|
||||
import { Octicons } from "@expo/vector-icons";
|
||||
@@ -51,11 +54,7 @@ export default function AdminCollaborationPublish() {
|
||||
<StackCustom>
|
||||
<AdminComp_BoxTitle title="Publish" />
|
||||
|
||||
<AdminTitleTable
|
||||
title1="Aksi"
|
||||
title2="Username"
|
||||
title3="Judul Proyek"
|
||||
/>
|
||||
<GridSpan_NewComponent text1={<TextCustom bold>Username</TextCustom>} text2={<TextCustom bold>Judul Proyek</TextCustom>} />
|
||||
{/* <Spacing height={10} /> */}
|
||||
<Divider />
|
||||
|
||||
@@ -68,32 +67,26 @@ export default function AdminCollaborationPublish() {
|
||||
) : (
|
||||
list?.map((item: any, index: number) => (
|
||||
<View key={index}>
|
||||
<AdminTableValue
|
||||
value1={
|
||||
<ActionIcon
|
||||
icon={
|
||||
<Octicons
|
||||
name="eye"
|
||||
size={ICON_SIZE_BUTTON}
|
||||
color="black"
|
||||
/>
|
||||
}
|
||||
onPress={() => {
|
||||
router.push(`/admin/collaboration/${item?.id}/publish`);
|
||||
}}
|
||||
/>
|
||||
}
|
||||
value2={
|
||||
<TextCustom align="center" truncate={1}>
|
||||
{item?.Author?.username || "-"}{" "}
|
||||
</TextCustom>
|
||||
}
|
||||
value3={
|
||||
<TextCustom align="center" truncate={2}>
|
||||
{item?.title || "-"}
|
||||
</TextCustom>
|
||||
}
|
||||
/>
|
||||
<ClickableCustom
|
||||
onPress={() => {
|
||||
router.push(`/admin/collaboration/${item?.id}/publish`);
|
||||
}}
|
||||
>
|
||||
<GridSpan_NewComponent
|
||||
text1={
|
||||
<TextCustom truncate={1}>
|
||||
{item?.Author?.username || "-"}{" "}
|
||||
</TextCustom>
|
||||
}
|
||||
text2={
|
||||
<TextCustom truncate={2}>
|
||||
{item?.title || "-"}
|
||||
</TextCustom>
|
||||
}
|
||||
/>
|
||||
</ClickableCustom>
|
||||
<Spacing height={8}/>
|
||||
<Divider/>
|
||||
</View>
|
||||
))
|
||||
)}
|
||||
|
||||
@@ -18,7 +18,7 @@ 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 { GridSpan_4_8 } from "@/components/_ShareComponent/GridSpan_4_8";
|
||||
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";
|
||||
@@ -195,7 +195,7 @@ export default function AdminDonationDetail() {
|
||||
|
||||
<StackCustom gap={5}>
|
||||
{listPencarianDana.map((item, i) => (
|
||||
<GridDetail_4_8
|
||||
<GridSpan_4_8
|
||||
key={i}
|
||||
label={<TextCustom bold>{item.label}</TextCustom>}
|
||||
value={<TextCustom>{item.value}</TextCustom>}
|
||||
@@ -236,7 +236,7 @@ export default function AdminDonationDetail() {
|
||||
<Spacing />
|
||||
|
||||
<StackCustom gap={"xs"}>
|
||||
<GridDetail_4_8
|
||||
<GridSpan_4_8
|
||||
label={<TextCustom bold>Jumlah Donatur</TextCustom>}
|
||||
value={
|
||||
<TextCustom>
|
||||
@@ -244,7 +244,7 @@ export default function AdminDonationDetail() {
|
||||
</TextCustom>
|
||||
}
|
||||
/>
|
||||
<GridDetail_4_8
|
||||
<GridSpan_4_8
|
||||
label={<TextCustom bold>Dana Terkumpul</TextCustom>}
|
||||
value={
|
||||
<TextCustom>
|
||||
@@ -261,7 +261,7 @@ export default function AdminDonationDetail() {
|
||||
<StackCustom>
|
||||
<DummyLandscapeImage imageId={data?.imageId || ""} />
|
||||
{listData.map((item, i) => (
|
||||
<GridDetail_4_8
|
||||
<GridSpan_4_8
|
||||
key={i}
|
||||
label={<TextCustom bold>{item.label}</TextCustom>}
|
||||
value={<TextCustom>{item.value}</TextCustom>}
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButtonAntTitle";
|
||||
import { GridDetail_4_8 } from "@/components/_ShareComponent/GridDetail_4_8";
|
||||
import { GridSpan_4_8 } from "@/components/_ShareComponent/GridSpan_4_8";
|
||||
import {
|
||||
apiAdminDonationInvoiceDetailById,
|
||||
apiAdminDonationInvoiceUpdateById,
|
||||
@@ -177,7 +177,7 @@ export default function AdminDonasiTransactionDetail() {
|
||||
<BaseBox>
|
||||
<StackCustom>
|
||||
{listData.map((item, index) => (
|
||||
<GridDetail_4_8
|
||||
<GridSpan_4_8
|
||||
key={index}
|
||||
label={<TextCustom bold>{item.label}</TextCustom>}
|
||||
value={<TextCustom>{item.value}</TextCustom>}
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButtonAntTitle";
|
||||
import { GridDetail_4_8 } from "@/components/_ShareComponent/GridDetail_4_8";
|
||||
import { GridSpan_4_8 } from "@/components/_ShareComponent/GridSpan_4_8";
|
||||
import { apiAdminDonationDisbursementOfFundsListById } from "@/service/api-admin/api-admin-donation";
|
||||
import { dateTimeView } from "@/utils/dateTimeView";
|
||||
import { formatCurrencyDisplay } from "@/utils/formatCurrencyDisplay";
|
||||
@@ -67,7 +67,7 @@ export default function AdminDonationDetailDisbursementOfFunds() {
|
||||
<BaseBox>
|
||||
<StackCustom>
|
||||
{listData?.map((item, index) => (
|
||||
<GridDetail_4_8
|
||||
<GridSpan_4_8
|
||||
key={index}
|
||||
label={<TextCustom bold>{item.label}</TextCustom>}
|
||||
value={<TextCustom>{item.value}</TextCustom>}
|
||||
|
||||
@@ -1,17 +1,56 @@
|
||||
import {
|
||||
BoxButtonOnFooter,
|
||||
ButtonCustom,
|
||||
StackCustom,
|
||||
TextCustom,
|
||||
TextInputCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButtonAntTitle";
|
||||
import { MainColor } from "@/constants/color-palet";
|
||||
import { apiAdminMasterDonationCategoryCreate } from "@/service/api-admin/api-master-admin";
|
||||
import { useRouter } from "expo-router";
|
||||
import { useState } from "react";
|
||||
import { Switch } from "react-native-paper";
|
||||
import Toast from "react-native-toast-message";
|
||||
|
||||
export default function AdminDonationCategoryCreate() {
|
||||
const router = useRouter();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [data, setData] = useState({
|
||||
name: "",
|
||||
active: false,
|
||||
});
|
||||
|
||||
const onSubmit = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const response = await apiAdminMasterDonationCategoryCreate({ data });
|
||||
if (response.success) {
|
||||
Toast.show({
|
||||
type: "success",
|
||||
text2: "Data berhasil disimpan",
|
||||
});
|
||||
router.back();
|
||||
return;
|
||||
}
|
||||
|
||||
Toast.show({
|
||||
type: "error",
|
||||
text1: "Gagal menyimpan data",
|
||||
});
|
||||
} catch (error) {
|
||||
console.log("[Error]", error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const buttonSubmit = (
|
||||
<BoxButtonOnFooter>
|
||||
<ButtonCustom onPress={() => router.back()}>Simpan</ButtonCustom>
|
||||
<ButtonCustom isLoading={loading} onPress={onSubmit}>
|
||||
Simpan
|
||||
</ButtonCustom>
|
||||
</BoxButtonOnFooter>
|
||||
);
|
||||
return (
|
||||
@@ -20,7 +59,23 @@ export default function AdminDonationCategoryCreate() {
|
||||
headerComponent={<AdminBackButtonAntTitle title="Tambah Kategori" />}
|
||||
footerComponent={buttonSubmit}
|
||||
>
|
||||
<TextInputCustom placeholder="Masukkan Kategori" />
|
||||
<TextInputCustom
|
||||
label=""
|
||||
placeholder="Masukkan Kategori"
|
||||
value={data.name}
|
||||
onChangeText={(text) => setData({ ...data, name: text })}
|
||||
/>
|
||||
<StackCustom gap={"sm"}>
|
||||
<TextCustom>Status</TextCustom>
|
||||
<Switch
|
||||
style={{
|
||||
alignSelf: "flex-start",
|
||||
}}
|
||||
color={MainColor.yellow}
|
||||
value={data.active}
|
||||
onValueChange={(value) => setData({ ...data, active: value })}
|
||||
/>
|
||||
</StackCustom>
|
||||
</ViewWrapper>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -1,21 +1,76 @@
|
||||
import {
|
||||
AlertDefaultSystem,
|
||||
BoxButtonOnFooter,
|
||||
ButtonCustom,
|
||||
StackCustom,
|
||||
TextCustom,
|
||||
TextInputCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButtonAntTitle";
|
||||
import { MainColor } from "@/constants/color-palet";
|
||||
import {
|
||||
apiAdminMasterDonationCategoryById,
|
||||
apiAdminMasterDonationCategoryUpdate,
|
||||
} from "@/service/api-admin/api-master-admin";
|
||||
import { useLocalSearchParams, useRouter } from "expo-router";
|
||||
import { useState } from "react";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { Switch } from "react-native-paper";
|
||||
|
||||
export default function AdminDonationCategoryUpdate() {
|
||||
const router = useRouter();
|
||||
const { id } = useLocalSearchParams();
|
||||
const [value, setValue] = useState(id);
|
||||
|
||||
const router = useRouter();
|
||||
const [data, setData] = useState<any>(null);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchData = async () => {
|
||||
const response = await apiAdminMasterDonationCategoryById({
|
||||
id: id as any,
|
||||
});
|
||||
console.log(JSON.stringify(response.data, null, 2));
|
||||
|
||||
setData(response.data);
|
||||
};
|
||||
fetchData();
|
||||
}, [id]);
|
||||
|
||||
const handlerSubmit = async () => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
const response = await apiAdminMasterDonationCategoryUpdate({
|
||||
id: id as any,
|
||||
data: data,
|
||||
});
|
||||
console.log(JSON.stringify(response.data, null, 2));
|
||||
router.back();
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const buttonSubmit = (
|
||||
<BoxButtonOnFooter>
|
||||
<ButtonCustom onPress={() => router.back()}>Update</ButtonCustom>
|
||||
<ButtonCustom
|
||||
disabled={isLoading || data?.name === ""}
|
||||
isLoading={isLoading}
|
||||
onPress={() => {
|
||||
AlertDefaultSystem({
|
||||
title: "Update Data",
|
||||
message: "Apakah anda yakin ingin mengupdate data ini?",
|
||||
textLeft: "Batal",
|
||||
textRight: "Ya",
|
||||
onPressLeft: () => {},
|
||||
onPressRight: () => handlerSubmit(),
|
||||
});
|
||||
}}
|
||||
>
|
||||
Update
|
||||
</ButtonCustom>
|
||||
</BoxButtonOnFooter>
|
||||
);
|
||||
return (
|
||||
@@ -25,10 +80,28 @@ export default function AdminDonationCategoryUpdate() {
|
||||
footerComponent={buttonSubmit}
|
||||
>
|
||||
<TextInputCustom
|
||||
label="Nama Kategori"
|
||||
placeholder="Masukkan Kategori"
|
||||
value={value as any}
|
||||
onChangeText={setValue}
|
||||
value={data?.name}
|
||||
onChangeText={(value) => setData({ ...data, name: value })}
|
||||
/>
|
||||
<StackCustom
|
||||
gap={"sm"}
|
||||
style={{
|
||||
alignContent: "flex-start",
|
||||
}}
|
||||
>
|
||||
<TextCustom>Status</TextCustom>
|
||||
|
||||
<Switch
|
||||
style={{
|
||||
alignSelf: "flex-start",
|
||||
}}
|
||||
color={MainColor.yellow}
|
||||
value={data?.active}
|
||||
onValueChange={(value) => setData({ ...data, active: value })}
|
||||
/>
|
||||
</StackCustom>
|
||||
</ViewWrapper>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -1,27 +1,65 @@
|
||||
import {
|
||||
ActionIcon,
|
||||
BaseBox,
|
||||
CenterCustom,
|
||||
Spacing,
|
||||
StackCustom,
|
||||
TextCustom,
|
||||
ViewWrapper,
|
||||
BadgeCustom,
|
||||
CenterCustom,
|
||||
ClickableCustom,
|
||||
Spacing,
|
||||
StackCustom,
|
||||
TextCustom,
|
||||
ViewWrapper
|
||||
} from "@/components";
|
||||
import { IconEdit } from "@/components/_Icon";
|
||||
import AdminActionIconPlus from "@/components/_ShareComponent/Admin/ActionIconPlus";
|
||||
import AdminComp_BoxTitle from "@/components/_ShareComponent/Admin/BoxTitlePage";
|
||||
import AdminTitlePage from "@/components/_ShareComponent/Admin/TitlePage";
|
||||
import { GridView_3_3_6 } from "@/components/_ShareComponent/GridView_3_3_6";
|
||||
import { MainColor } from "@/constants/color-palet";
|
||||
import { ICON_SIZE_BUTTON } from "@/constants/constans-value";
|
||||
import { View } from "react-native";
|
||||
import { Divider, Switch } from "react-native-paper";
|
||||
import { router } from "expo-router";
|
||||
import { GridSpan_4_8 } from "@/components/_ShareComponent/GridSpan_4_8";
|
||||
import { apiAdminMasterDonationCategory } from "@/service/api-admin/api-master-admin";
|
||||
import { colorActivationForBadge } from "@/utils/colorActivationForBadge";
|
||||
import { router, useFocusEffect } from "expo-router";
|
||||
import { useCallback, useState } from "react";
|
||||
import { RefreshControl, View } from "react-native";
|
||||
import { Divider } from "react-native-paper";
|
||||
|
||||
export default function AdminDonationCategory() {
|
||||
const [listData, setListData] = useState<any[]>([]);
|
||||
const [refreshing, setRefreshing] = useState(false);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
fetchMaster();
|
||||
}, [])
|
||||
);
|
||||
|
||||
const fetchMaster = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const response = await apiAdminMasterDonationCategory();
|
||||
if (response.success) {
|
||||
console.log(JSON.stringify(response.data, null, 2));
|
||||
setListData(response.data);
|
||||
} else {
|
||||
setListData([]);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("[Error]", error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const onRefresh = async () => {
|
||||
setRefreshing(true);
|
||||
await fetchMaster();
|
||||
setRefreshing(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<ViewWrapper headerComponent={<AdminTitlePage title="Donasi" />}>
|
||||
<ViewWrapper
|
||||
refreshControl={
|
||||
<RefreshControl refreshing={refreshing} onRefresh={onRefresh} />
|
||||
}
|
||||
headerComponent={<AdminTitlePage title="Donasi" />}
|
||||
>
|
||||
<AdminComp_BoxTitle
|
||||
title="Kategori"
|
||||
rightComponent={
|
||||
@@ -33,81 +71,65 @@ export default function AdminDonationCategory() {
|
||||
}
|
||||
/>
|
||||
|
||||
<BaseBox>
|
||||
<GridView_3_3_6
|
||||
component1={
|
||||
<TextCustom bold align="center">
|
||||
Aksi
|
||||
</TextCustom>
|
||||
}
|
||||
component2={<TextCustom bold>Status</TextCustom>}
|
||||
component3={<TextCustom bold>Kategori</TextCustom>}
|
||||
<View>
|
||||
<GridSpan_4_8
|
||||
label={<TextCustom bold>Status</TextCustom>}
|
||||
value={<TextCustom bold>Kategori</TextCustom>}
|
||||
/>
|
||||
{/* <Grid>
|
||||
<Grid.Col style={{ paddingLeft: 10 }} span={4}>
|
||||
<TextCustom bold>Status</TextCustom>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={8}>
|
||||
<TextCustom bold>Kategori</TextCustom>
|
||||
</Grid.Col>
|
||||
</Grid> */}
|
||||
|
||||
<Divider />
|
||||
<Spacing />
|
||||
|
||||
<StackCustom>
|
||||
{listData.map((item, index) => (
|
||||
<View key={index}>
|
||||
<GridView_3_3_6
|
||||
component1={
|
||||
<ClickableCustom
|
||||
onPress={() => {
|
||||
router.push(`/admin/donation/category-update?id=${item.id}`);
|
||||
}}
|
||||
key={index}
|
||||
>
|
||||
<GridSpan_4_8
|
||||
label={
|
||||
<CenterCustom>
|
||||
<ActionIcon
|
||||
icon={
|
||||
<IconEdit size={ICON_SIZE_BUTTON} color="black" />
|
||||
}
|
||||
onPress={() => {
|
||||
router.push(`/admin/donation/category-update?id=${index}`);
|
||||
}}
|
||||
/>
|
||||
<BadgeCustom
|
||||
color={colorActivationForBadge({
|
||||
status: item.active,
|
||||
})}
|
||||
>
|
||||
{item.active ? "Aktif" : "Tidak Aktif"}
|
||||
</BadgeCustom>
|
||||
</CenterCustom>
|
||||
}
|
||||
component2={
|
||||
<Switch
|
||||
value={true}
|
||||
onValueChange={(item) => {
|
||||
console.log(item);
|
||||
}}
|
||||
color={MainColor.yellow}
|
||||
|
||||
/>
|
||||
}
|
||||
component3={<TextCustom bold>{item.label}</TextCustom>}
|
||||
value={<TextCustom>{item.name}</TextCustom>}
|
||||
/>
|
||||
<Spacing height={10} />
|
||||
{/* <Grid containerStyle={{ paddingBottom: 10 }}>
|
||||
<Grid.Col span={4} style={{ paddingLeft: 10 }}>
|
||||
<CenterCustom>
|
||||
<BadgeCustom
|
||||
color={item.active ? MainColor.green : MainColor.red}
|
||||
>
|
||||
{item.active ? "Aktif" : "Tidak Aktif"}
|
||||
</BadgeCustom>
|
||||
</CenterCustom>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={8}>
|
||||
<TextCustom bold>{item.name}</TextCustom>
|
||||
</Grid.Col>
|
||||
</Grid> */}
|
||||
<Divider />
|
||||
</View>
|
||||
</ClickableCustom>
|
||||
))}
|
||||
</StackCustom>
|
||||
</BaseBox>
|
||||
</View>
|
||||
</ViewWrapper>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const listData = [
|
||||
{
|
||||
label: "Kegiatan Sosial",
|
||||
value: "kegiatan_sosial",
|
||||
},
|
||||
{
|
||||
label: "Pendidikan",
|
||||
value: "pendidikan",
|
||||
},
|
||||
{
|
||||
label: "Kesehatan",
|
||||
value: "kesehatan",
|
||||
},
|
||||
{
|
||||
label: "Kebudayaan",
|
||||
value: "kebudayaan",
|
||||
},
|
||||
{
|
||||
label: "Bencana Alami",
|
||||
value: "bencana_alami",
|
||||
},
|
||||
{
|
||||
label: "Lainnya",
|
||||
value: "lainnya",
|
||||
},
|
||||
];
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
ActionIcon,
|
||||
AlertDefaultSystem,
|
||||
BadgeCustom,
|
||||
BaseBox,
|
||||
DrawerCustom,
|
||||
LoaderCustom,
|
||||
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 { GridSpan_4_8 } from "@/components/_ShareComponent/GridSpan_4_8";
|
||||
import ReportBox from "@/components/Box/ReportBox";
|
||||
import { ICON_SIZE_BUTTON } from "@/constants/constans-value";
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
@@ -39,6 +39,11 @@ export default function AdminEventDetail() {
|
||||
const [data, setData] = React.useState<any | null>(null);
|
||||
const [loadData, setLoadData] = React.useState(false);
|
||||
const deepLinkURL = `${DEEP_LINK_URL}/event/${id}/confirmation?userId=${user?.id}`;
|
||||
const deepLinkURLDEV = `${DEEP_LINK_URL}/--/event/${id}/confirmation?userId=${user?.id}`;
|
||||
|
||||
const isDevLink = process.env.NODE_ENV === "development" ? deepLinkURLDEV : deepLinkURL;
|
||||
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
onLoadData();
|
||||
@@ -156,7 +161,7 @@ export default function AdminEventDetail() {
|
||||
<BaseBox>
|
||||
<StackCustom>
|
||||
{listData.map((item, i) => (
|
||||
<GridDetail_4_8
|
||||
<GridSpan_4_8
|
||||
key={i}
|
||||
label={<TextCustom bold>{item.label}</TextCustom>}
|
||||
value={<TextCustom>{item.value}</TextCustom>}
|
||||
@@ -181,7 +186,7 @@ export default function AdminEventDetail() {
|
||||
<LoaderCustom />
|
||||
) : (
|
||||
<QRCode
|
||||
value={deepLinkURL}
|
||||
value={isDevLink}
|
||||
size={200}
|
||||
// logo={require("@/assets/images/logo-hipmi.png")}
|
||||
// logoSize={70}
|
||||
@@ -191,7 +196,7 @@ export default function AdminEventDetail() {
|
||||
/>
|
||||
)}
|
||||
|
||||
<TextCustom align="center">{deepLinkURL}</TextCustom>
|
||||
<TextCustom align="center">{isDevLink}</TextCustom>
|
||||
</StackCustom>
|
||||
</BaseBox>
|
||||
)}
|
||||
|
||||
@@ -10,14 +10,17 @@ import {
|
||||
} from "@/components";
|
||||
import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButtonAntTitle";
|
||||
import { apiAdminEventListOfParticipants } from "@/service/api-admin/api-admin-event";
|
||||
import dayjs, { Dayjs } from "dayjs";
|
||||
import { useFocusEffect, useLocalSearchParams } from "expo-router";
|
||||
import _ from "lodash";
|
||||
import { View } from "moti";
|
||||
import { useCallback, useState } from "react";
|
||||
|
||||
export default function AdminEventListOfParticipants() {
|
||||
const { id } = useLocalSearchParams();
|
||||
const [listData, setListData] = useState<any[] | null>(null);
|
||||
const [loadData, setLoadData] = useState(false);
|
||||
const [startDate, setStartDate] = useState<Dayjs | undefined>();
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
@@ -32,8 +35,11 @@ export default function AdminEventListOfParticipants() {
|
||||
id: id as string,
|
||||
});
|
||||
|
||||
console.log("[DATA]", JSON.stringify(response, null, 2));
|
||||
|
||||
if (response.success) {
|
||||
setListData(response.data);
|
||||
setStartDate(dayjs(response.data.Event.tanggal));
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
@@ -42,7 +48,6 @@ export default function AdminEventListOfParticipants() {
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
<ViewWrapper
|
||||
@@ -60,17 +65,35 @@ export default function AdminEventListOfParticipants() {
|
||||
<Grid>
|
||||
<Grid.Col span={6}>
|
||||
<StackCustom gap={"sm"}>
|
||||
<TextCustom bold truncate>{item?.User?.username}</TextCustom>
|
||||
<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>
|
||||
{startDate &&
|
||||
startDate.subtract(1, "hour").diff(dayjs()) < 0 ? (
|
||||
<BadgeCustom
|
||||
style={{ alignSelf: "flex-end" }}
|
||||
color={item?.isPresent ? "green" : "red"}
|
||||
>
|
||||
{item?.isPresent ? "Hadir" : "Tidak Hadir"}
|
||||
</BadgeCustom>
|
||||
) : (
|
||||
<View
|
||||
style={{
|
||||
justifyContent: "flex-end",
|
||||
}}
|
||||
>
|
||||
<BadgeCustom
|
||||
style={{ alignSelf: "flex-end" }}
|
||||
color="gray"
|
||||
>
|
||||
-
|
||||
</BadgeCustom>
|
||||
</View>
|
||||
)}
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
</BaseBox>
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
ActionIcon,
|
||||
ClickableCustom,
|
||||
LoaderCustom,
|
||||
SearchInput,
|
||||
StackCustom,
|
||||
TextCustom,
|
||||
ViewWrapper
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import AdminComp_BoxTitle from "@/components/_ShareComponent/Admin/BoxTitlePage";
|
||||
import AdminTitleTable from "@/components/_ShareComponent/Admin/TableTitle";
|
||||
@@ -13,6 +14,7 @@ import AdminTableValue from "@/components/_ShareComponent/Admin/TableValue";
|
||||
import AdminTitlePage from "@/components/_ShareComponent/Admin/TitlePage";
|
||||
import { ICON_SIZE_BUTTON } from "@/constants/constans-value";
|
||||
import { apiAdminEvent } from "@/service/api-admin/api-admin-event";
|
||||
import { dateTimeView } from "@/utils/dateTimeView";
|
||||
import { Octicons } from "@expo/vector-icons";
|
||||
import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
|
||||
import _ from "lodash";
|
||||
@@ -74,8 +76,8 @@ export default function AdminEventStatus() {
|
||||
|
||||
<StackCustom gap={"sm"}>
|
||||
<AdminTitleTable
|
||||
title1="Aksi"
|
||||
title2="Username"
|
||||
title1="Username"
|
||||
title2="Tanggal"
|
||||
title3="Judul Event"
|
||||
/>
|
||||
<Divider />
|
||||
@@ -83,36 +85,47 @@ export default function AdminEventStatus() {
|
||||
{loadData ? (
|
||||
<LoaderCustom />
|
||||
) : _.isEmpty(listData) ? (
|
||||
<TextCustom align="center" size="small" color="gray">Belum ada data</TextCustom>
|
||||
<TextCustom align="center" size="small" color="gray">
|
||||
Belum ada data
|
||||
</TextCustom>
|
||||
) : (
|
||||
listData?.map((item, index) => (
|
||||
<AdminTableValue
|
||||
<ClickableCustom
|
||||
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>
|
||||
}
|
||||
/>
|
||||
onPress={() => {
|
||||
router.push(`/admin/event/${item.id}/${status}`);
|
||||
}}
|
||||
>
|
||||
<AdminTableValue
|
||||
key={index}
|
||||
value1={
|
||||
<TextCustom truncate={1}>
|
||||
{item?.Author?.username || "-"}
|
||||
</TextCustom>
|
||||
// <ActionIcon
|
||||
// icon={
|
||||
// <Octicons
|
||||
// name="eye"
|
||||
// size={ICON_SIZE_BUTTON}
|
||||
// color="black"
|
||||
// />
|
||||
// }
|
||||
// onPress={() => {
|
||||
// router.push(`/admin/event/${item.id}/${status}`);
|
||||
// }}
|
||||
// />
|
||||
}
|
||||
value2={
|
||||
<TextCustom truncate={1}>
|
||||
{dateTimeView({ date: item?.tanggal })}
|
||||
</TextCustom>
|
||||
}
|
||||
value3={
|
||||
<TextCustom truncate={2}>{item?.title || "-"}</TextCustom>
|
||||
}
|
||||
/>
|
||||
<Divider/>
|
||||
</ClickableCustom>
|
||||
))
|
||||
)}
|
||||
</StackCustom>
|
||||
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
} from "@/components";
|
||||
import { IconDot } from "@/components/_Icon/IconComponent";
|
||||
import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButtonAntTitle";
|
||||
import { GridDetail_4_8 } from "@/components/_ShareComponent/GridDetail_4_8";
|
||||
import { GridSpan_4_8 } from "@/components/_ShareComponent/GridSpan_4_8";
|
||||
import { MainColor } from "@/constants/color-palet";
|
||||
import { ICON_SIZE_XLARGE } from "@/constants/constans-value";
|
||||
import { apiAdminForumPostingById } from "@/service/api-admin/api-admin-forum";
|
||||
@@ -103,7 +103,7 @@ export default function AdminForumDetailPosting() {
|
||||
<BaseBox>
|
||||
<StackCustom gap={"sm"}>
|
||||
{listDataAction.map((item, i) => (
|
||||
<GridDetail_4_8
|
||||
<GridSpan_4_8
|
||||
key={i}
|
||||
label={<TextCustom bold>{item.label}</TextCustom>}
|
||||
value={<TextCustom>{item.value}</TextCustom>}
|
||||
|
||||
@@ -3,6 +3,7 @@ import {
|
||||
ActionIcon,
|
||||
AlertDefaultSystem,
|
||||
BaseBox,
|
||||
CenterCustom,
|
||||
DrawerCustom,
|
||||
LoaderCustom,
|
||||
MenuDrawerDynamicGrid,
|
||||
@@ -16,7 +17,8 @@ import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButt
|
||||
import AdminComp_BoxTitle from "@/components/_ShareComponent/Admin/BoxTitlePage";
|
||||
import AdminTitleTable from "@/components/_ShareComponent/Admin/TableTitle";
|
||||
import AdminTableValue from "@/components/_ShareComponent/Admin/TableValue";
|
||||
import { GridDetail_4_8 } from "@/components/_ShareComponent/GridDetail_4_8";
|
||||
import { GridSpan_4_8 } from "@/components/_ShareComponent/GridSpan_4_8";
|
||||
import { GridSpan_NewComponent } from "@/components/_ShareComponent/GridSpan_NewComponent";
|
||||
import { MainColor } from "@/constants/color-palet";
|
||||
import { ICON_SIZE_BUTTON } from "@/constants/constans-value";
|
||||
import {
|
||||
@@ -27,6 +29,7 @@ import {
|
||||
import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
|
||||
import _ from "lodash";
|
||||
import { useCallback, useState } from "react";
|
||||
import { View } from "react-native";
|
||||
import { Divider } from "react-native-paper";
|
||||
import Toast from "react-native-toast-message";
|
||||
|
||||
@@ -95,24 +98,24 @@ export default function AdminForumReportComment() {
|
||||
>
|
||||
<BaseBox>
|
||||
<StackCustom gap={"sm"}>
|
||||
<GridDetail_4_8
|
||||
label={<TextCustom bold>Username</TextCustom>}
|
||||
value={<TextCustom>{data?.Author?.username || "-"}</TextCustom>}
|
||||
<GridSpan_NewComponent
|
||||
text1={<TextCustom bold>Username</TextCustom>}
|
||||
text2={<TextCustom>{data?.Author?.username || "-"}</TextCustom>}
|
||||
/>
|
||||
<GridDetail_4_8
|
||||
label={<TextCustom bold>Komentar</TextCustom>}
|
||||
value={<TextCustom>{data?.komentar || "-"}</TextCustom>}
|
||||
<GridSpan_NewComponent
|
||||
text1={<TextCustom bold>Komentar</TextCustom>}
|
||||
text2={<TextCustom>{data?.komentar || "-"}</TextCustom>}
|
||||
/>
|
||||
</StackCustom>
|
||||
</BaseBox>
|
||||
|
||||
<AdminComp_BoxTitle title="Daftar Report Komentar" />
|
||||
|
||||
<StackCustom>
|
||||
<AdminTitleTable
|
||||
title1="Aksi"
|
||||
title2="Pelapor"
|
||||
title3="Kategori Report"
|
||||
<StackCustom gap={"sm"}>
|
||||
<GridSpan_NewComponent
|
||||
text1={<TextCustom bold align="center">Aksi</TextCustom>}
|
||||
text2={<TextCustom bold>Pelapor</TextCustom>}
|
||||
text3={<TextCustom bold>Kategori Report</TextCustom>}
|
||||
/>
|
||||
<Divider />
|
||||
{loadList ? (
|
||||
@@ -123,34 +126,39 @@ export default function AdminForumReportComment() {
|
||||
</TextCustom>
|
||||
) : (
|
||||
listReport?.map((item: any, index: number) => (
|
||||
<AdminTableValue
|
||||
key={index}
|
||||
value1={
|
||||
<ActionIcon
|
||||
icon={<IconView size={ICON_SIZE_BUTTON} color="black" />}
|
||||
onPress={() => {
|
||||
setOpenDrawerAction(true);
|
||||
setSelectedReport({
|
||||
id: item.id,
|
||||
username: item.User?.username,
|
||||
kategori: item.ForumMaster_KategoriReport?.title,
|
||||
keterangan: item.ForumMaster_KategoriReport?.deskripsi,
|
||||
deskripsi: item.deskripsi,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
}
|
||||
value2={
|
||||
<TextCustom truncate={1}>
|
||||
{item?.User?.username || "-"}
|
||||
</TextCustom>
|
||||
}
|
||||
value3={
|
||||
<TextCustom truncate={2} align="center">
|
||||
{item?.ForumMaster_KategoriReport?.title || "-"}
|
||||
</TextCustom>
|
||||
}
|
||||
/>
|
||||
<View key={index}>
|
||||
<GridSpan_NewComponent
|
||||
text1={
|
||||
<CenterCustom>
|
||||
<ActionIcon
|
||||
icon={<IconView size={ICON_SIZE_BUTTON} color="black" />}
|
||||
onPress={() => {
|
||||
setOpenDrawerAction(true);
|
||||
setSelectedReport({
|
||||
id: item.id,
|
||||
username: item.User?.username,
|
||||
kategori: item.ForumMaster_KategoriReport?.title,
|
||||
keterangan:
|
||||
item.ForumMaster_KategoriReport?.deskripsi,
|
||||
deskripsi: item.deskripsi,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</CenterCustom>
|
||||
}
|
||||
text2={
|
||||
<TextCustom truncate={1}>
|
||||
{item?.User?.username || "-"}
|
||||
</TextCustom>
|
||||
}
|
||||
text3={
|
||||
<TextCustom truncate={2}>
|
||||
{item?.ForumMaster_KategoriReport?.title || "-"}
|
||||
</TextCustom>
|
||||
}
|
||||
/>
|
||||
<Divider />
|
||||
</View>
|
||||
))
|
||||
)}
|
||||
</StackCustom>
|
||||
@@ -208,20 +216,20 @@ export default function AdminForumReportComment() {
|
||||
height={"auto"}
|
||||
>
|
||||
<StackCustom>
|
||||
<GridDetail_4_8
|
||||
<GridSpan_4_8
|
||||
label={<TextCustom bold>Pelapor</TextCustom>}
|
||||
value={<TextCustom>{selectedReport?.username || "-"}</TextCustom>}
|
||||
/>
|
||||
|
||||
{selectedReport?.kategori && (
|
||||
<>
|
||||
<GridDetail_4_8
|
||||
<GridSpan_4_8
|
||||
label={<TextCustom bold>Kategori Report</TextCustom>}
|
||||
value={
|
||||
<TextCustom>{selectedReport?.kategori || "-"}</TextCustom>
|
||||
}
|
||||
/>
|
||||
<GridDetail_4_8
|
||||
<GridSpan_4_8
|
||||
label={<TextCustom bold>Keterangan</TextCustom>}
|
||||
value={
|
||||
<TextCustom>{selectedReport?.keterangan || "-"}</TextCustom>
|
||||
@@ -231,7 +239,7 @@ export default function AdminForumReportComment() {
|
||||
)}
|
||||
|
||||
{selectedReport?.deskripsi && (
|
||||
<GridDetail_4_8
|
||||
<GridSpan_4_8
|
||||
label={<TextCustom bold>Deskripsi</TextCustom>}
|
||||
value={
|
||||
<TextCustom>{selectedReport?.deskripsi || "-"}</TextCustom>
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
AlertDefaultSystem,
|
||||
BadgeCustom,
|
||||
BaseBox,
|
||||
CenterCustom,
|
||||
DrawerCustom,
|
||||
LoaderCustom,
|
||||
MenuDrawerDynamicGrid,
|
||||
@@ -17,7 +18,8 @@ import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButt
|
||||
import AdminComp_BoxTitle from "@/components/_ShareComponent/Admin/BoxTitlePage";
|
||||
import AdminTitleTable from "@/components/_ShareComponent/Admin/TableTitle";
|
||||
import AdminTableValue from "@/components/_ShareComponent/Admin/TableValue";
|
||||
import { GridDetail_4_8 } from "@/components/_ShareComponent/GridDetail_4_8";
|
||||
import { GridSpan_4_8 } from "@/components/_ShareComponent/GridSpan_4_8";
|
||||
import { GridSpan_NewComponent } from "@/components/_ShareComponent/GridSpan_NewComponent";
|
||||
import { MainColor } from "@/constants/color-palet";
|
||||
import { ICON_SIZE_BUTTON } from "@/constants/constans-value";
|
||||
import {
|
||||
@@ -28,6 +30,7 @@ import {
|
||||
import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
|
||||
import _ from "lodash";
|
||||
import { useCallback, useState } from "react";
|
||||
import { View } from "react-native";
|
||||
import { Divider } from "react-native-paper";
|
||||
import Toast from "react-native-toast-message";
|
||||
|
||||
@@ -95,14 +98,14 @@ export default function AdminForumReportPosting() {
|
||||
>
|
||||
<BaseBox>
|
||||
<StackCustom gap={"sm"}>
|
||||
<GridDetail_4_8
|
||||
label={<TextCustom bold>Username</TextCustom>}
|
||||
value={<TextCustom>{data?.Author?.username || "-"}</TextCustom>}
|
||||
<GridSpan_NewComponent
|
||||
text1={<TextCustom bold>Username</TextCustom>}
|
||||
text2={<TextCustom>{data?.Author?.username || "-"}</TextCustom>}
|
||||
/>
|
||||
|
||||
<GridDetail_4_8
|
||||
label={<TextCustom bold>Status</TextCustom>}
|
||||
value={
|
||||
<GridSpan_NewComponent
|
||||
text1={<TextCustom bold>Status</TextCustom>}
|
||||
text2={
|
||||
data && data?.ForumMaster_StatusPosting?.status ? (
|
||||
<BadgeCustom
|
||||
color={
|
||||
@@ -121,19 +124,23 @@ export default function AdminForumReportPosting() {
|
||||
}
|
||||
/>
|
||||
|
||||
<GridDetail_4_8
|
||||
label={<TextCustom bold>Postingan</TextCustom>}
|
||||
value={<TextCustom>{data?.diskusi || "-"}</TextCustom>}
|
||||
<GridSpan_NewComponent
|
||||
text1={<TextCustom bold>Postingan</TextCustom>}
|
||||
text2={<TextCustom>{data?.diskusi || "-"}</TextCustom>}
|
||||
/>
|
||||
</StackCustom>
|
||||
</BaseBox>
|
||||
|
||||
<AdminComp_BoxTitle title="Daftar Report Posting" />
|
||||
<StackCustom gap={"sm"}>
|
||||
<AdminTitleTable
|
||||
title1="Aksi"
|
||||
title2="Pelapor"
|
||||
title3="Kategori Report"
|
||||
<GridSpan_NewComponent
|
||||
text1={
|
||||
<TextCustom bold align="center">
|
||||
Aksi
|
||||
</TextCustom>
|
||||
}
|
||||
text2={<TextCustom bold>Pelapor</TextCustom>}
|
||||
text3={<TextCustom bold>Kategori Report</TextCustom>}
|
||||
/>
|
||||
<Divider />
|
||||
{loadListReport ? (
|
||||
@@ -144,34 +151,41 @@ export default function AdminForumReportPosting() {
|
||||
</TextCustom>
|
||||
) : (
|
||||
listReport?.map((item: any, index: number) => (
|
||||
<AdminTableValue
|
||||
key={index}
|
||||
value1={
|
||||
<ActionIcon
|
||||
icon={<IconView size={ICON_SIZE_BUTTON} color="black" />}
|
||||
onPress={() => {
|
||||
setOpenDrawerAction(true);
|
||||
setSelectedReport({
|
||||
id: item?.id,
|
||||
username: item?.User?.username,
|
||||
kategori: item?.ForumMaster_KategoriReport?.title,
|
||||
keterangan: item?.ForumMaster_KategoriReport?.deskripsi,
|
||||
deskripsi: item?.deskripsi,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
}
|
||||
value2={
|
||||
<TextCustom truncate={1}>
|
||||
{item?.User?.username || "-"}
|
||||
</TextCustom>
|
||||
}
|
||||
value3={
|
||||
<TextCustom truncate={2} align="center">
|
||||
{item?.ForumMaster_KategoriReport?.title || "-"}
|
||||
</TextCustom>
|
||||
}
|
||||
/>
|
||||
<View key={index}>
|
||||
<GridSpan_NewComponent
|
||||
text1={
|
||||
<CenterCustom>
|
||||
<ActionIcon
|
||||
icon={
|
||||
<IconView size={ICON_SIZE_BUTTON} color="black" />
|
||||
}
|
||||
onPress={() => {
|
||||
setOpenDrawerAction(true);
|
||||
setSelectedReport({
|
||||
id: item?.id,
|
||||
username: item?.User?.username,
|
||||
kategori: item?.ForumMaster_KategoriReport?.title,
|
||||
keterangan:
|
||||
item?.ForumMaster_KategoriReport?.deskripsi,
|
||||
deskripsi: item?.deskripsi,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</CenterCustom>
|
||||
}
|
||||
text2={
|
||||
<TextCustom truncate>
|
||||
{item?.User?.username || "-"}
|
||||
</TextCustom>
|
||||
}
|
||||
text3={
|
||||
<TextCustom truncate={2}>
|
||||
{item?.ForumMaster_KategoriReport?.title || "-"}
|
||||
</TextCustom>
|
||||
}
|
||||
/>
|
||||
<Divider />
|
||||
</View>
|
||||
))
|
||||
)}
|
||||
</StackCustom>
|
||||
@@ -229,20 +243,20 @@ export default function AdminForumReportPosting() {
|
||||
height={"auto"}
|
||||
>
|
||||
<StackCustom>
|
||||
<GridDetail_4_8
|
||||
<GridSpan_4_8
|
||||
label={<TextCustom bold>Pelapor</TextCustom>}
|
||||
value={<TextCustom>{selectedReport?.username || "-"}</TextCustom>}
|
||||
/>
|
||||
|
||||
{selectedReport?.kategori && (
|
||||
<>
|
||||
<GridDetail_4_8
|
||||
<GridSpan_4_8
|
||||
label={<TextCustom bold>Kategori Report</TextCustom>}
|
||||
value={
|
||||
<TextCustom>{selectedReport?.kategori || "-"}</TextCustom>
|
||||
}
|
||||
/>
|
||||
<GridDetail_4_8
|
||||
<GridSpan_4_8
|
||||
label={<TextCustom bold>Keterangan</TextCustom>}
|
||||
value={
|
||||
<TextCustom>{selectedReport?.keterangan || "-"}</TextCustom>
|
||||
@@ -252,7 +266,7 @@ export default function AdminForumReportPosting() {
|
||||
)}
|
||||
|
||||
{selectedReport?.deskripsi && (
|
||||
<GridDetail_4_8
|
||||
<GridSpan_4_8
|
||||
label={<TextCustom bold>Deskripsi</TextCustom>}
|
||||
value={
|
||||
<TextCustom>{selectedReport?.deskripsi || "-"}</TextCustom>
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
ActionIcon,
|
||||
ClickableCustom,
|
||||
LoaderCustom,
|
||||
SearchInput,
|
||||
Spacing,
|
||||
StackCustom,
|
||||
TextCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import { IconView } from "@/components/_Icon/IconComponent";
|
||||
import AdminComp_BoxTitle from "@/components/_ShareComponent/Admin/BoxTitlePage";
|
||||
import AdminTitleTable from "@/components/_ShareComponent/Admin/TableTitle";
|
||||
import AdminTableValue from "@/components/_ShareComponent/Admin/TableValue";
|
||||
import AdminTitlePage from "@/components/_ShareComponent/Admin/TitlePage";
|
||||
import { ICON_SIZE_BUTTON } from "@/constants/constans-value";
|
||||
import { GridSpan_4_8 } from "@/components/_ShareComponent/GridSpan_4_8";
|
||||
import { GridSpan_NewComponent } from "@/components/_ShareComponent/GridSpan_NewComponent";
|
||||
import { apiAdminForum } from "@/service/api-admin/api-admin-forum";
|
||||
import { router, useFocusEffect } from "expo-router";
|
||||
import _ from "lodash";
|
||||
import React, { useCallback, useState } from "react";
|
||||
import { useCallback, useState } from "react";
|
||||
import { View } from "react-native";
|
||||
import { Divider } from "react-native-paper";
|
||||
|
||||
export default function AdminForumPosting() {
|
||||
@@ -37,7 +37,9 @@ export default function AdminForumPosting() {
|
||||
category: "posting",
|
||||
search: search,
|
||||
});
|
||||
|
||||
|
||||
console.log("DATA", JSON.stringify(response, null, 2));
|
||||
|
||||
if (response.success) {
|
||||
setList(response.data);
|
||||
}
|
||||
@@ -51,7 +53,7 @@ export default function AdminForumPosting() {
|
||||
const rightComponent = (
|
||||
<SearchInput
|
||||
containerStyle={{ width: "100%", marginBottom: 0 }}
|
||||
placeholder="Cari"
|
||||
placeholder="Cari postingan"
|
||||
value={search}
|
||||
onChangeText={setSearch}
|
||||
/>
|
||||
@@ -61,9 +63,15 @@ export default function AdminForumPosting() {
|
||||
<>
|
||||
<ViewWrapper headerComponent={<AdminTitlePage title="Forum" />}>
|
||||
<AdminComp_BoxTitle title={"Posting"} rightComponent={rightComponent} />
|
||||
<GridSpan_NewComponent
|
||||
text1={<TextCustom bold truncate>Username</TextCustom>}
|
||||
text2={<TextCustom bold truncate> Postingan</TextCustom>}
|
||||
text3={<TextCustom bold align="center" truncate> Report Posting</TextCustom>}
|
||||
text4={<TextCustom bold align="center" truncate> Komentar</TextCustom>}
|
||||
/>
|
||||
<Divider />
|
||||
<Spacing />
|
||||
<StackCustom>
|
||||
<AdminTitleTable title1="Aksi" title2="Username" title3="Postingan" />
|
||||
<Divider />
|
||||
{loadList ? (
|
||||
<LoaderCustom />
|
||||
) : _.isEmpty(list) ? (
|
||||
@@ -72,25 +80,38 @@ export default function AdminForumPosting() {
|
||||
</TextCustom>
|
||||
) : (
|
||||
list?.map((item: any, index: number) => (
|
||||
<AdminTableValue
|
||||
key={index}
|
||||
value1={
|
||||
<ActionIcon
|
||||
icon={<IconView size={ICON_SIZE_BUTTON} color="black" />}
|
||||
onPress={() => {
|
||||
router.push(`/admin/forum/${item?.id}`);
|
||||
}}
|
||||
<View key={index}>
|
||||
<ClickableCustom
|
||||
onPress={() => {
|
||||
router.push(`/admin/forum/${item.id}`);
|
||||
}}
|
||||
>
|
||||
<GridSpan_NewComponent
|
||||
text1={
|
||||
<TextCustom truncate={1}>
|
||||
{item?.Author?.username || "-"}
|
||||
</TextCustom>
|
||||
}
|
||||
text2={
|
||||
<TextCustom truncate>
|
||||
{item?.diskusi || "-"}
|
||||
</TextCustom>
|
||||
}
|
||||
text3={
|
||||
<TextCustom align="center" truncate={2}>
|
||||
{item?.reportPosting || "-"}
|
||||
</TextCustom>
|
||||
}
|
||||
text4={
|
||||
<TextCustom align="center" truncate={2}>
|
||||
{item?.komentar || "-"}
|
||||
</TextCustom>
|
||||
}
|
||||
/>
|
||||
}
|
||||
value2={
|
||||
<TextCustom truncate={1}>
|
||||
{item?.Author?.username || "-"}
|
||||
</TextCustom>
|
||||
}
|
||||
value3={
|
||||
<TextCustom truncate={2}>{item?.diskusi || "-"}</TextCustom>
|
||||
}
|
||||
/>
|
||||
|
||||
</ClickableCustom>
|
||||
<Divider />
|
||||
</View>
|
||||
))
|
||||
)}
|
||||
</StackCustom>
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
ActionIcon,
|
||||
ClickableCustom,
|
||||
LoaderCustom,
|
||||
SearchInput,
|
||||
Spacing,
|
||||
StackCustom,
|
||||
TextCustom,
|
||||
ViewWrapper,
|
||||
@@ -12,12 +14,14 @@ import AdminComp_BoxTitle from "@/components/_ShareComponent/Admin/BoxTitlePage"
|
||||
import AdminTitleTable from "@/components/_ShareComponent/Admin/TableTitle";
|
||||
import AdminTableValue from "@/components/_ShareComponent/Admin/TableValue";
|
||||
import AdminTitlePage from "@/components/_ShareComponent/Admin/TitlePage";
|
||||
import { GridSpan_NewComponent } from "@/components/_ShareComponent/GridSpan_NewComponent";
|
||||
import { MainColor } from "@/constants/color-palet";
|
||||
import { ICON_SIZE_BUTTON } from "@/constants/constans-value";
|
||||
import { apiAdminForum } from "@/service/api-admin/api-admin-forum";
|
||||
import { router, useFocusEffect } from "expo-router";
|
||||
import _ from "lodash";
|
||||
import { useCallback, useState } from "react";
|
||||
import { View } from "react-native";
|
||||
import { Divider } from "react-native-paper";
|
||||
|
||||
export default function AdminForumReportComment() {
|
||||
@@ -67,13 +71,26 @@ export default function AdminForumReportComment() {
|
||||
rightComponent={rightComponent}
|
||||
/>
|
||||
|
||||
<StackCustom gap={"sm"}>
|
||||
<AdminTitleTable
|
||||
title1="Aksi"
|
||||
title2="Pelapor"
|
||||
title3="Jenis Laporan"
|
||||
/>
|
||||
<Divider />
|
||||
<GridSpan_NewComponent
|
||||
text1={
|
||||
<TextCustom bold truncate>
|
||||
Pelapor
|
||||
</TextCustom>
|
||||
}
|
||||
text2={
|
||||
<TextCustom bold truncate>
|
||||
Komentar
|
||||
</TextCustom>
|
||||
}
|
||||
text3={
|
||||
<TextCustom bold truncate>
|
||||
Jenis Laporan
|
||||
</TextCustom>
|
||||
}
|
||||
/>
|
||||
<Divider />
|
||||
<Spacing />
|
||||
<StackCustom gap={"lg"}>
|
||||
{loadList ? (
|
||||
<LoaderCustom />
|
||||
) : _.isEmpty(listData) ? (
|
||||
@@ -82,34 +99,35 @@ export default function AdminForumReportComment() {
|
||||
</TextCustom>
|
||||
) : (
|
||||
listData?.map((item: any, index: number) => (
|
||||
<AdminTableValue
|
||||
key={index}
|
||||
value1={
|
||||
<ActionIcon
|
||||
icon={
|
||||
<IconView
|
||||
size={ICON_SIZE_BUTTON}
|
||||
color={MainColor.black}
|
||||
/>
|
||||
<View key={index}>
|
||||
<ClickableCustom
|
||||
onPress={() => {
|
||||
router.push(
|
||||
`/admin/forum/${item?.Forum_Komentar?.id}/list-report-comment`
|
||||
);
|
||||
}}
|
||||
>
|
||||
<GridSpan_NewComponent
|
||||
text1={
|
||||
<TextCustom truncate={1}>
|
||||
{item?.User?.username || "-"}
|
||||
</TextCustom>
|
||||
}
|
||||
text2={
|
||||
<TextCustom truncate={2}>
|
||||
{item?.Forum_Komentar?.komentar || "-"}
|
||||
</TextCustom>
|
||||
}
|
||||
text3={
|
||||
<TextCustom truncate={2}>
|
||||
{item?.ForumMaster_KategoriReport?.title || "-"}
|
||||
</TextCustom>
|
||||
}
|
||||
onPress={() => {
|
||||
router.push(
|
||||
`/admin/forum/${item?.Forum_Komentar?.id}/list-report-comment`
|
||||
);
|
||||
}}
|
||||
/>
|
||||
}
|
||||
value2={
|
||||
<TextCustom truncate={1}>
|
||||
{item?.User?.username || "-"}
|
||||
</TextCustom>
|
||||
}
|
||||
value3={
|
||||
<TextCustom truncate={2} align="center">
|
||||
{item?.ForumMaster_KategoriReport?.title || "-"}
|
||||
</TextCustom>
|
||||
}
|
||||
/>
|
||||
</ClickableCustom>
|
||||
<Spacing />
|
||||
<Divider />
|
||||
</View>
|
||||
))
|
||||
)}
|
||||
</StackCustom>
|
||||
|
||||
@@ -1,24 +1,27 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
ActionIcon,
|
||||
ClickableCustom,
|
||||
Divider,
|
||||
LoaderCustom,
|
||||
SearchInput,
|
||||
StackCustom,
|
||||
TextCustom,
|
||||
ViewWrapper
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import { IconView } from "@/components/_Icon/IconComponent";
|
||||
import AdminComp_BoxTitle from "@/components/_ShareComponent/Admin/BoxTitlePage";
|
||||
import AdminTitleTable from "@/components/_ShareComponent/Admin/TableTitle";
|
||||
import AdminTableValue from "@/components/_ShareComponent/Admin/TableValue";
|
||||
import AdminTitlePage from "@/components/_ShareComponent/Admin/TitlePage";
|
||||
import { GridSpan_NewComponent } from "@/components/_ShareComponent/GridSpan_NewComponent";
|
||||
import { MainColor } from "@/constants/color-palet";
|
||||
import { ICON_SIZE_BUTTON } from "@/constants/constans-value";
|
||||
import { apiAdminForum } from "@/service/api-admin/api-admin-forum";
|
||||
import { router, useFocusEffect } from "expo-router";
|
||||
import _ from "lodash";
|
||||
import { useCallback, useState } from "react";
|
||||
import { View } from "react-native";
|
||||
|
||||
export default function AdminForumReportPosting() {
|
||||
const [listData, setListData] = useState<any[] | null>(null);
|
||||
@@ -67,46 +70,51 @@ export default function AdminForumReportPosting() {
|
||||
rightComponent={rightComponent}
|
||||
/>
|
||||
|
||||
<StackCustom gap={"sm"}>
|
||||
<AdminTitleTable title1="Aksi" title2="Pelapor" title3="Postingan" />
|
||||
|
||||
<Divider />
|
||||
<GridSpan_NewComponent
|
||||
text1={
|
||||
<TextCustom bold truncate>
|
||||
Username
|
||||
</TextCustom>
|
||||
}
|
||||
text2={
|
||||
<TextCustom bold truncate>
|
||||
Postingan
|
||||
</TextCustom>
|
||||
}
|
||||
/>
|
||||
<Divider />
|
||||
<StackCustom>
|
||||
{loadList ? (
|
||||
<LoaderCustom />
|
||||
) : _.isEmpty(listData) ? (
|
||||
<TextCustom align="center" color="gray">
|
||||
<TextCustom align="center" color="gray">
|
||||
Belum ada data
|
||||
</TextCustom>
|
||||
) : (
|
||||
listData?.map((item: any, index: number) => (
|
||||
<AdminTableValue
|
||||
key={index}
|
||||
value1={
|
||||
<ActionIcon
|
||||
icon={
|
||||
<IconView
|
||||
size={ICON_SIZE_BUTTON}
|
||||
color={MainColor.black}
|
||||
/>
|
||||
<View key={index}>
|
||||
<ClickableCustom
|
||||
onPress={() => {
|
||||
router.push(
|
||||
`/admin/forum/${item?.Forum_Posting?.id}/list-report-posting`
|
||||
);
|
||||
}}
|
||||
>
|
||||
<GridSpan_NewComponent
|
||||
text1={
|
||||
<TextCustom truncate={1}>
|
||||
{item?.User?.username || "-"}
|
||||
</TextCustom>
|
||||
}
|
||||
text2={
|
||||
<TextCustom truncate={1}>
|
||||
{item?.Forum_Posting?.diskusi || "-"}
|
||||
</TextCustom>
|
||||
}
|
||||
onPress={() => {
|
||||
router.push(
|
||||
`/admin/forum/${item?.Forum_Posting?.id}/list-report-posting`
|
||||
);
|
||||
}}
|
||||
/>
|
||||
}
|
||||
value2={
|
||||
<TextCustom truncate={1}>
|
||||
{item?.User?.username || "-"}
|
||||
</TextCustom>
|
||||
}
|
||||
value3={
|
||||
<TextCustom truncate={2} align="center">
|
||||
{item?.Forum_Posting?.diskusi || "-"}
|
||||
</TextCustom>
|
||||
}
|
||||
/>
|
||||
</ClickableCustom>
|
||||
<Divider />
|
||||
</View>
|
||||
))
|
||||
)}
|
||||
</StackCustom>
|
||||
|
||||
@@ -19,7 +19,7 @@ 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 { GridSpan_4_8 } from "@/components/_ShareComponent/GridSpan_4_8";
|
||||
import ReportBox from "@/components/Box/ReportBox";
|
||||
import { MainColor } from "@/constants/color-palet";
|
||||
import { ICON_SIZE_BUTTON } from "@/constants/constans-value";
|
||||
@@ -183,7 +183,7 @@ export default function AdminInvestmentDetail() {
|
||||
/>
|
||||
<Spacing />
|
||||
<StackCustom gap={"xs"}>
|
||||
<GridDetail_4_8
|
||||
<GridSpan_4_8
|
||||
label={<TextCustom bold>Sisa Saham</TextCustom>}
|
||||
value={
|
||||
<TextCustom>
|
||||
@@ -191,7 +191,7 @@ export default function AdminInvestmentDetail() {
|
||||
</TextCustom>
|
||||
}
|
||||
/>
|
||||
<GridDetail_4_8
|
||||
<GridSpan_4_8
|
||||
label={<TextCustom bold>Validasi Transaksi</TextCustom>}
|
||||
value={
|
||||
<TextCustom>
|
||||
@@ -207,7 +207,7 @@ export default function AdminInvestmentDetail() {
|
||||
<StackCustom>
|
||||
<DummyLandscapeImage imageId={data?.imageId} />
|
||||
{listData.map((item, i) => (
|
||||
<GridDetail_4_8
|
||||
<GridSpan_4_8
|
||||
key={i}
|
||||
label={<TextCustom bold>{item.label}</TextCustom>}
|
||||
value={<TextCustom>{item.value}</TextCustom>}
|
||||
@@ -218,7 +218,7 @@ export default function AdminInvestmentDetail() {
|
||||
|
||||
<BaseBox>
|
||||
<StackCustom>
|
||||
<GridDetail_4_8
|
||||
<GridSpan_4_8
|
||||
label={<TextCustom bold>File Prospektus</TextCustom>}
|
||||
value={
|
||||
<ButtonCustom
|
||||
@@ -238,7 +238,7 @@ export default function AdminInvestmentDetail() {
|
||||
</ButtonCustom>
|
||||
}
|
||||
/>
|
||||
<GridDetail_4_8
|
||||
<GridSpan_4_8
|
||||
label={<TextCustom bold>File Dokumen</TextCustom>}
|
||||
value={
|
||||
<StackCustom>
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButtonAntTitle";
|
||||
import { GridDetail_4_8 } from "@/components/_ShareComponent/GridDetail_4_8";
|
||||
import { GridSpan_4_8 } from "@/components/_ShareComponent/GridSpan_4_8";
|
||||
import GridTwoView from "@/components/_ShareComponent/GridTwoView";
|
||||
import { MainColor } from "@/constants/color-palet";
|
||||
import {
|
||||
@@ -225,7 +225,7 @@ export default function AdminInvestmentTransactionDetail() {
|
||||
<BaseBox>
|
||||
<StackCustom>
|
||||
{listData.map((item, index) => (
|
||||
<GridDetail_4_8
|
||||
<GridSpan_4_8
|
||||
key={index}
|
||||
label={<TextCustom bold>{item.label}</TextCustom>}
|
||||
value={<TextCustom>{item.value}</TextCustom>}
|
||||
|
||||
@@ -76,7 +76,7 @@ export default function SuperAdmin_ListUser() {
|
||||
</TextCustom>
|
||||
}
|
||||
component2={
|
||||
<TextCustom align="center" bold>
|
||||
<TextCustom bold>
|
||||
Username
|
||||
</TextCustom>
|
||||
}
|
||||
|
||||
@@ -73,11 +73,7 @@ export default function AdminUserAccess() {
|
||||
Aksi
|
||||
</TextCustom>
|
||||
}
|
||||
component2={
|
||||
<TextCustom align="center" bold>
|
||||
Username
|
||||
</TextCustom>
|
||||
}
|
||||
component2={<TextCustom bold>Username</TextCustom>}
|
||||
component3={
|
||||
<TextCustom align="center" bold>
|
||||
Status Akses
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
/* 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 { GridSpan_4_8 } from "@/components/_ShareComponent/GridSpan_4_8";
|
||||
import ReportBox from "@/components/Box/ReportBox";
|
||||
import { MainColor } from "@/constants/color-palet";
|
||||
import funUpdateStatusVoting from "@/screens/Admin/Voting/funUpdateStatus";
|
||||
@@ -169,7 +169,7 @@ export default function AdminVotingDetail() {
|
||||
<BaseBox>
|
||||
<StackCustom>
|
||||
{listData.map((item, i) => (
|
||||
<GridDetail_4_8
|
||||
<GridSpan_4_8
|
||||
key={i}
|
||||
label={<TextCustom bold>{item.label}</TextCustom>}
|
||||
value={<TextCustom>{item.value}</TextCustom>}
|
||||
|
||||
@@ -103,7 +103,7 @@ export default function AdminVotingStatus() {
|
||||
</TextCustom>
|
||||
}
|
||||
value3={
|
||||
<TextCustom align="center" truncate={2}>
|
||||
<TextCustom truncate={2}>
|
||||
{item?.title || "-"}
|
||||
</TextCustom>
|
||||
}
|
||||
|
||||
@@ -91,7 +91,7 @@ export default function AdminVotingHistory() {
|
||||
}
|
||||
value2={<TextCustom truncate={1}>{item?.Author?.username || "-"}</TextCustom>}
|
||||
value3={
|
||||
<TextCustom align="center" truncate={2}>
|
||||
<TextCustom truncate={2}>
|
||||
{item?.title || "-"}
|
||||
</TextCustom>
|
||||
}
|
||||
|
||||
116
app/(application)/terms-agreement.tsx
Normal file
116
app/(application)/terms-agreement.tsx
Normal file
@@ -0,0 +1,116 @@
|
||||
import {
|
||||
BoxButtonOnFooter,
|
||||
ButtonCustom,
|
||||
CheckboxCustom,
|
||||
InformationBox,
|
||||
StackCustom,
|
||||
ViewWrapper
|
||||
} from "@/components";
|
||||
import { MainColor } from "@/constants/color-palet";
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
import { apiAcceptTermService, BASE_URL } from "@/service/api-config";
|
||||
import { GStyles } from "@/styles/global-styles";
|
||||
import { openBrowser } from "@/utils/openBrower";
|
||||
import { Stack } from "expo-router";
|
||||
import { useState } from "react";
|
||||
import { Text, View } from "react-native";
|
||||
import Toast from "react-native-toast-message";
|
||||
|
||||
export default function TermsAgreement() {
|
||||
const { user, logout } = useAuth();
|
||||
const [term, setTerm] = useState(false);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const url = BASE_URL;
|
||||
|
||||
const handleSubmit = async () => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
const response = await apiAcceptTermService({
|
||||
data: {
|
||||
id: user?.id as string,
|
||||
termsOfServiceAccepted: term,
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.success) {
|
||||
Toast.show({
|
||||
type: "error",
|
||||
text1: "Gagal",
|
||||
text2: response.message,
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
Toast.show({
|
||||
type: "success",
|
||||
text1: "Anda berhasil menerima syarat & ketentuan",
|
||||
text2: "Silahkan login kembali",
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
logout();
|
||||
}, 2000);
|
||||
} catch (error) {
|
||||
console.log("error", error);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const footerComponent = (
|
||||
<BoxButtonOnFooter>
|
||||
<ButtonCustom
|
||||
onPress={handleSubmit}
|
||||
disabled={!term}
|
||||
isLoading={isLoading}
|
||||
>
|
||||
Setuju
|
||||
</ButtonCustom>
|
||||
</BoxButtonOnFooter>
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Stack.Screen
|
||||
options={{
|
||||
title: "Terms & Conditions",
|
||||
}}
|
||||
/>
|
||||
<ViewWrapper footerComponent={footerComponent}>
|
||||
<StackCustom>
|
||||
<InformationBox text="Anda dialihkan ke halaman syarat & ketentuan, karena Anda belum menerima persetujuan syarat & ketentuan." />
|
||||
|
||||
<View
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
marginTop: 16,
|
||||
marginBottom: 16,
|
||||
paddingInline: 10,
|
||||
}}
|
||||
>
|
||||
<CheckboxCustom value={term} onChange={() => setTerm(!term)} />
|
||||
|
||||
<Text style={GStyles.textLabel}>
|
||||
Saya setuju dengan{" "}
|
||||
<Text
|
||||
style={{
|
||||
color: MainColor.yellow,
|
||||
textDecorationLine: "underline",
|
||||
}}
|
||||
onPress={() => {
|
||||
const toUrl = `${url}/terms-of-service.html`;
|
||||
openBrowser(toUrl);
|
||||
}}
|
||||
>
|
||||
Syarat & Ketentuan
|
||||
</Text>{" "}
|
||||
yang melarang konten tidak pantas dan perilaku merugikan.
|
||||
</Text>
|
||||
</View>
|
||||
</StackCustom>
|
||||
</ViewWrapper>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,4 +1,6 @@
|
||||
import NotificationInitializer from "@/components/Notification/NotificationInitializer";
|
||||
import { AuthProvider } from "@/context/AuthContext";
|
||||
import { NotificationProvider } from "@/hooks/use-notification-store";
|
||||
import AppRoot from "@/screens/RootLayout/AppRoot";
|
||||
import "react-native-gesture-handler";
|
||||
import { SafeAreaProvider } from "react-native-safe-area-context";
|
||||
|
||||
16
components/Alert/AlertWarning.ts
Normal file
16
components/Alert/AlertWarning.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { Alert } from "react-native";
|
||||
|
||||
export default function AlertWarning({
|
||||
title = "Peringatan Bagi Pengguna !",
|
||||
description = "Konten yang Anda masukkan mengandung kata-kata yang tidak sesuai dengan pedoman komunitas kami. Mohon gunakan bahasa yang sopan dan menghargai sesama pengguna. Jika kata tersebut sebenarnya lumrah, mohon maaf—kemungkinan sistem kami belum mengenalnya sebagai wajar.",
|
||||
}: {
|
||||
title?: string
|
||||
description?: string;
|
||||
}) {
|
||||
return Alert.alert(title, description, [
|
||||
{
|
||||
text: "Tutup",
|
||||
onPress: () => {},
|
||||
},
|
||||
]);
|
||||
}
|
||||
@@ -32,9 +32,10 @@ const FloatingButton: React.FC<FloatingButtonProps> = ({
|
||||
const styles = StyleSheet.create({
|
||||
fab: {
|
||||
position: "absolute",
|
||||
margin: 16,
|
||||
margin: "auto",
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
// bottom: 10,
|
||||
top: -20,
|
||||
backgroundColor: AccentColor.softblue, // Warna Twitter biru
|
||||
borderRadius: 50,
|
||||
borderColor: AccentColor.blue,
|
||||
|
||||
@@ -53,6 +53,8 @@ const DateTimeInput_Android: React.FC<DateTimeInputProps> = ({
|
||||
const [selectedDate, setSelectedDate] = useState<Date>(value as any);
|
||||
const [selectedTime, setSelectedTime] = useState<Date>(value as any);
|
||||
|
||||
console.log("Date Android", value)
|
||||
|
||||
// Fungsi untuk menggabungkan tanggal dan waktu
|
||||
const combineDateAndTime = useCallback((date: Date, time: Date): Date => {
|
||||
const combined = new Date(date);
|
||||
|
||||
@@ -7,7 +7,7 @@ import DateTimePicker, {
|
||||
} from "@react-native-community/datetimepicker";
|
||||
import dayjs from "dayjs";
|
||||
import React, { useState } from "react";
|
||||
import { StyleProp, Text, View, ViewStyle } from "react-native";
|
||||
import { Button, StyleProp, Text, View, ViewStyle } from "react-native";
|
||||
import ClickableCustom from "../Clickable/ClickableCustom";
|
||||
import TextCustom from "../Text/TextCustom";
|
||||
|
||||
@@ -129,24 +129,64 @@ const DateTimeInput_IOS: React.FC<DateTimeInputProps> = ({
|
||||
borderWidth: 1,
|
||||
}}
|
||||
>
|
||||
<View style={{ alignItems: "flex-end" }}>
|
||||
{/* <View style={{ alignItems: "flex-start" }}>
|
||||
<Ionicons
|
||||
name="close"
|
||||
size={20}
|
||||
color="black"
|
||||
onPress={() => setShow(false)}
|
||||
onPress={() => {
|
||||
setShow(false);
|
||||
setSelectedDate(undefined);
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
</View> */}
|
||||
|
||||
<DateTimePicker
|
||||
value={selectedDate || new Date()}
|
||||
mode={"datetime"}
|
||||
display="inline"
|
||||
display="spinner"
|
||||
onChange={handleConfirm}
|
||||
minimumDate={minimumDate}
|
||||
maximumDate={maximumDate}
|
||||
themeVariant="light"
|
||||
/>
|
||||
<View style={{ flexDirection: "row", gap: 10 }}>
|
||||
<ClickableCustom
|
||||
onPress={() => {
|
||||
setShow(false)
|
||||
setSelectedDate(undefined)
|
||||
}}
|
||||
style={{
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
padding: 12,
|
||||
borderRadius: 10,
|
||||
backgroundColor: MainColor.placeholder,
|
||||
marginTop: 10,
|
||||
width: "48%",
|
||||
}}
|
||||
>
|
||||
<TextCustom color="black">Batal</TextCustom>
|
||||
</ClickableCustom>
|
||||
|
||||
<ClickableCustom
|
||||
onPress={() => {
|
||||
setShow(false)
|
||||
onChange(selectedDate as any)
|
||||
}}
|
||||
style={{
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
padding: 12,
|
||||
borderRadius: 10,
|
||||
backgroundColor: MainColor.darkblue,
|
||||
marginTop: 10,
|
||||
width: "48%",
|
||||
}}
|
||||
>
|
||||
<TextCustom>OK</TextCustom>
|
||||
</ClickableCustom>
|
||||
</View>
|
||||
</View>
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -23,6 +23,9 @@ const DateTimePickerCustom: React.FC<Props> = ({
|
||||
minimumDate,
|
||||
disabled = false,
|
||||
}) => {
|
||||
|
||||
console.log("Date Android Comp", value)
|
||||
|
||||
return (
|
||||
<>
|
||||
{Platform.OS === "ios" ? (
|
||||
@@ -47,6 +50,9 @@ const DateTimePickerCustom: React.FC<Props> = ({
|
||||
maximumDate={maximumDate}
|
||||
minimumDate={minimumDate}
|
||||
disabled={disabled}
|
||||
value={value as DateTimePickerEvent | Date | null | any}
|
||||
|
||||
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
|
||||
111
components/Notification/NotificationInitializer.tsx
Normal file
111
components/Notification/NotificationInitializer.tsx
Normal file
@@ -0,0 +1,111 @@
|
||||
// src/components/NotificationInitializer.tsx
|
||||
import { useEffect } from "react";
|
||||
import { useForegroundNotifications } from "@/hooks/use-foreground-notifications";
|
||||
import { useNotificationStore } from "@/hooks/use-notification-store";
|
||||
import type { FirebaseMessagingTypes } from "@react-native-firebase/messaging";
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
import { Platform } from "react-native";
|
||||
import * as Device from "expo-device";
|
||||
import * as Application from "expo-application";
|
||||
import { apiDeviceRegisterToken } from "@/service/api-device-token";
|
||||
import messaging, {
|
||||
isSupported,
|
||||
requestPermission,
|
||||
getToken,
|
||||
AuthorizationStatus,
|
||||
} from "@react-native-firebase/messaging";
|
||||
|
||||
// ✅ Modular imports (sesuai v22+)
|
||||
|
||||
export default function NotificationInitializer() {
|
||||
// Setup handler notifikasi
|
||||
const { user, logout } = useAuth(); // dari AuthContext
|
||||
const { addNotification } = useNotificationStore();
|
||||
|
||||
// Ambil token FCM (opsional, hanya untuk log)
|
||||
useEffect(() => {
|
||||
if (!user) {
|
||||
console.log("User not available, skipping token sync");
|
||||
return;
|
||||
}
|
||||
|
||||
const registerDeviceToken = async () => {
|
||||
try {
|
||||
// ✅ Dapatkan instance messaging
|
||||
const messagingInstance = messaging();
|
||||
|
||||
// ✅ Gunakan instance sebagai argumen
|
||||
const supported = await isSupported(messagingInstance);
|
||||
if (!supported) {
|
||||
console.log("‼️ FCM tidak didukung");
|
||||
return;
|
||||
};
|
||||
|
||||
const authStatus = await requestPermission(messagingInstance);
|
||||
if (authStatus !== AuthorizationStatus.AUTHORIZED) {
|
||||
console.warn("Izin telah ditolak");
|
||||
return;
|
||||
}
|
||||
|
||||
const fcmToken = await getToken(messagingInstance);
|
||||
if (!fcmToken) {
|
||||
logout();
|
||||
return;
|
||||
}
|
||||
|
||||
console.log("✅ FCM Token:", fcmToken);
|
||||
|
||||
const platform = Platform.OS; // "ios" | "android"
|
||||
const model = Device.modelName || "unknown";
|
||||
const appVersion =
|
||||
(Application.nativeApplicationVersion || "unknown") +
|
||||
"-" +
|
||||
(Application.nativeBuildVersion || "unknown");
|
||||
const deviceId =
|
||||
Device.osInternalBuildId || Device.modelName + "-" + Date.now();
|
||||
|
||||
// Kirim ke backend
|
||||
await apiDeviceRegisterToken({
|
||||
data: {
|
||||
fcmToken,
|
||||
platform,
|
||||
deviceId,
|
||||
model,
|
||||
appVersion,
|
||||
userId: user?.id || "",
|
||||
},
|
||||
});
|
||||
|
||||
console.log("✅ Device token berhasil didaftarkan ke backend");
|
||||
} catch (error) {
|
||||
console.error("❌ Gagal mendaftarkan device token:", error);
|
||||
}
|
||||
};
|
||||
|
||||
registerDeviceToken();
|
||||
}, [user?.id]);
|
||||
|
||||
const handleForegroundNotification = (
|
||||
message: FirebaseMessagingTypes.RemoteMessage
|
||||
) => {
|
||||
const title = message.notification?.title || "Notifikasi";
|
||||
const body = message.notification?.body || "";
|
||||
const rawData = message.data || {};
|
||||
|
||||
const safeData: Record<string, string> = {};
|
||||
for (const key in rawData) {
|
||||
safeData[key] =
|
||||
typeof rawData[key] === "string"
|
||||
? rawData[key]
|
||||
: JSON.stringify(rawData[key]);
|
||||
}
|
||||
|
||||
console.log("📥 Menambahkan ke store:", { title, body, safeData });
|
||||
addNotification({ title, body, data: safeData , type: "notification", });
|
||||
console.log("✅ Notifikasi ditambahkan ke state");
|
||||
};
|
||||
|
||||
useForegroundNotifications(handleForegroundNotification);
|
||||
|
||||
return null; // komponen ini tidak merender apa-apa
|
||||
}
|
||||
@@ -87,7 +87,7 @@ const SelectCustom: React.FC<SelectProps> = ({
|
||||
borderRadius,
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
paddingHorizontal: 10,
|
||||
// paddingHorizontal: 0,
|
||||
height: 50,
|
||||
},
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { AccentColor, MainColor } from "@/constants/color-palet";
|
||||
import { GStyles } from "@/styles/global-styles";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import {
|
||||
@@ -6,7 +7,9 @@ import {
|
||||
Text,
|
||||
View,
|
||||
ViewStyle,
|
||||
useColorScheme,
|
||||
} from "react-native";
|
||||
import { PlaceholderColor } from "@/constants/color-palet";
|
||||
|
||||
type IconType = React.ReactNode | string;
|
||||
|
||||
@@ -48,7 +51,7 @@ const TextAreaCustom: React.FC<TextAreaCustomProps> = ({
|
||||
minRows = 4,
|
||||
maxRows = 6,
|
||||
showCount = false,
|
||||
maxLength,
|
||||
maxLength = 1000,
|
||||
value,
|
||||
onChangeText,
|
||||
height = 100,
|
||||
@@ -78,6 +81,9 @@ const TextAreaCustom: React.FC<TextAreaCustomProps> = ({
|
||||
);
|
||||
};
|
||||
|
||||
const colorScheme = useColorScheme();
|
||||
const theme = PlaceholderColor[colorScheme || "light"];
|
||||
|
||||
return (
|
||||
<View style={[GStyles.inputContainerArea]}>
|
||||
{label && (
|
||||
@@ -109,6 +115,7 @@ const TextAreaCustom: React.FC<TextAreaCustomProps> = ({
|
||||
GStyles.textAreaInput,
|
||||
{ color: fontColor },
|
||||
]}
|
||||
placeholderTextColor={theme.placeholder}
|
||||
editable={!disabled}
|
||||
value={value as string}
|
||||
onChangeText={onChangeText}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { PlaceholderColor } from "@/constants/color-palet";
|
||||
import { GStyles } from "@/styles/global-styles";
|
||||
import Ionicons from "@expo/vector-icons/Ionicons";
|
||||
import React, { useState } from "react";
|
||||
@@ -8,8 +9,10 @@ import {
|
||||
TouchableOpacity,
|
||||
View,
|
||||
ViewStyle,
|
||||
useColorScheme
|
||||
} from "react-native";
|
||||
|
||||
|
||||
type IconType = React.ReactNode | string;
|
||||
|
||||
type Props = {
|
||||
@@ -74,6 +77,9 @@ const TextInputCustom = ({
|
||||
}
|
||||
};
|
||||
|
||||
const colorScheme = useColorScheme();
|
||||
const theme = PlaceholderColor[colorScheme || "light"];
|
||||
|
||||
return (
|
||||
<View style={[GStyles.inputContainerArea, containerStyle]}>
|
||||
{label && (
|
||||
@@ -100,12 +106,14 @@ const TextInputCustom = ({
|
||||
{ color: fontColor },
|
||||
disabled && GStyles.inputPlaceholderDisabled, // <-- placeholder saat disabled
|
||||
]}
|
||||
placeholderTextColor={theme.placeholder}
|
||||
editable={!disabled}
|
||||
secureTextEntry={secureTextEntry && !isPasswordVisible}
|
||||
keyboardType={keyboardType}
|
||||
onChangeText={handleTextChange}
|
||||
maxLength={maxLength}
|
||||
{...rest}
|
||||
|
||||
/>
|
||||
{secureTextEntry && (
|
||||
<TouchableOpacity
|
||||
|
||||
@@ -98,13 +98,14 @@ export const IconView = ({
|
||||
);
|
||||
};
|
||||
|
||||
export const IconDot = ({ size, color }: { size?: number; color?: string }) => {
|
||||
export const IconDot = ({ size, color, onPress }: { size?: number; color?: string , onPress?: () => void}) => {
|
||||
return (
|
||||
<>
|
||||
<Ionicons
|
||||
name="ellipsis-vertical"
|
||||
size={size || ICON_SIZE_MEDIUM}
|
||||
color={color || MainColor.darkblue}
|
||||
onPress={onPress}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -4,12 +4,21 @@ import { Octicons } from "@expo/vector-icons";
|
||||
|
||||
export { IconPlus };
|
||||
|
||||
function IconPlus({ color, size }: { color?: string; size?: number }) {
|
||||
function IconPlus({
|
||||
color,
|
||||
size,
|
||||
onPress,
|
||||
}: {
|
||||
color?: string;
|
||||
size?: number;
|
||||
onPress?: () => void;
|
||||
}) {
|
||||
return (
|
||||
<Octicons
|
||||
name="plus-circle"
|
||||
size={size || ICON_SIZE_MEDIUM}
|
||||
color={color || MainColor.white}
|
||||
onPress={onPress}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ export default function AdminButtonReject({
|
||||
<>
|
||||
<ButtonCustom
|
||||
iconLeft={<IconReject size={16} />}
|
||||
backgroundColor={MainColor.red}
|
||||
backgroundColor={MainColor.orange}
|
||||
textColor="white"
|
||||
onPress={onReject}
|
||||
isLoading={isLoading}
|
||||
|
||||
@@ -1,17 +1,23 @@
|
||||
import Grid from "@/components/Grid/GridCustom";
|
||||
import React from "react";
|
||||
import { View } from "react-native";
|
||||
import { StyleProp, View, ViewStyle } from "react-native";
|
||||
import { Divider } from "react-native-paper";
|
||||
|
||||
export default function AdminTableValue({
|
||||
value1,
|
||||
value2,
|
||||
value3,
|
||||
style1,
|
||||
style2,
|
||||
style3,
|
||||
bottomLine = false,
|
||||
}: {
|
||||
value1: React.ReactNode;
|
||||
value2: React.ReactNode;
|
||||
value3: React.ReactNode;
|
||||
style1?: ViewStyle;
|
||||
style2?: ViewStyle;
|
||||
style3?: ViewStyle;
|
||||
bottomLine?: boolean;
|
||||
}) {
|
||||
return (
|
||||
@@ -25,6 +31,7 @@ export default function AdminTableValue({
|
||||
justifyContent: "center",
|
||||
paddingLeft: 10,
|
||||
paddingRight: 10,
|
||||
...style1,
|
||||
}}
|
||||
>
|
||||
{value1}
|
||||
@@ -36,6 +43,7 @@ export default function AdminTableValue({
|
||||
justifyContent: "center",
|
||||
paddingLeft: 10,
|
||||
paddingRight: 10,
|
||||
...style2,
|
||||
}}
|
||||
>
|
||||
{value2}
|
||||
@@ -44,9 +52,10 @@ export default function AdminTableValue({
|
||||
span={6}
|
||||
style={{
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
alignItems: "flex-start",
|
||||
paddingLeft: 10,
|
||||
paddingRight: 10,
|
||||
...style3,
|
||||
}}
|
||||
>
|
||||
{value3}
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
import { Grid } from "@/components";
|
||||
|
||||
export const GridDetail_4_8 = ({
|
||||
label,
|
||||
value,
|
||||
}: {
|
||||
label: React.ReactNode;
|
||||
value: React.ReactNode;
|
||||
}) => {
|
||||
return (
|
||||
<Grid>
|
||||
<Grid.Col span={4} style={{ justifyContent: "center", paddingRight: 10 }}>
|
||||
{label}
|
||||
</Grid.Col>
|
||||
<Grid.Col span={8} style={{ justifyContent: "center" }}>
|
||||
{value}
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
27
components/_ShareComponent/GridSpan_4_8.tsx
Normal file
27
components/_ShareComponent/GridSpan_4_8.tsx
Normal file
@@ -0,0 +1,27 @@
|
||||
import { Grid } from "@/components";
|
||||
|
||||
export const GridSpan_4_8 = ({
|
||||
label: text1,
|
||||
value: text2,
|
||||
}: {
|
||||
label: React.ReactNode;
|
||||
value: React.ReactNode;
|
||||
}) => {
|
||||
return (
|
||||
<Grid>
|
||||
<Grid.Col
|
||||
span={4}
|
||||
style={{
|
||||
justifyContent: "flex-start",
|
||||
paddingRight: 8,
|
||||
paddingLeft: 8,
|
||||
}}
|
||||
>
|
||||
{text1}
|
||||
</Grid.Col>
|
||||
<Grid.Col span={8} style={{ justifyContent: "center", paddingRight: 8 }}>
|
||||
{text2}
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
54
components/_ShareComponent/GridSpan_NewComponent.tsx
Normal file
54
components/_ShareComponent/GridSpan_NewComponent.tsx
Normal file
@@ -0,0 +1,54 @@
|
||||
import { Grid } from "@/components";
|
||||
|
||||
export const GridSpan_NewComponent = ({
|
||||
text1,
|
||||
text2,
|
||||
text3,
|
||||
text4,
|
||||
span1,
|
||||
span2,
|
||||
}: {
|
||||
text1: React.ReactNode;
|
||||
text2: React.ReactNode;
|
||||
text3?: React.ReactNode;
|
||||
text4?: React.ReactNode;
|
||||
span1?: number;
|
||||
span2?: number;
|
||||
}) => {
|
||||
return (
|
||||
<Grid>
|
||||
<Grid.Col
|
||||
span={span1 ? span1 : text4 ? 3 : 4}
|
||||
style={{
|
||||
justifyContent: "flex-start",
|
||||
paddingRight: 5,
|
||||
paddingLeft: 3,
|
||||
}}
|
||||
>
|
||||
{text1}
|
||||
</Grid.Col>
|
||||
<Grid.Col
|
||||
span={span2 ? span2 : text4 ? 3 : text3 ? 4 : 8}
|
||||
style={{ justifyContent: "flex-start", paddingRight: 5 }}
|
||||
>
|
||||
{text2}
|
||||
</Grid.Col>
|
||||
{text3 && (
|
||||
<Grid.Col
|
||||
span={text4 ? 3 : 4}
|
||||
style={{ justifyContent: "flex-start", paddingRight: 5 }}
|
||||
>
|
||||
{text3}
|
||||
</Grid.Col>
|
||||
)}
|
||||
{text4 && (
|
||||
<Grid.Col
|
||||
span={3}
|
||||
style={{ justifyContent: "flex-start", paddingRight: 5 }}
|
||||
>
|
||||
{text4}
|
||||
</Grid.Col>
|
||||
)}
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
20
components/_ShareComponent/ListEmptyComponent.tsx
Normal file
20
components/_ShareComponent/ListEmptyComponent.tsx
Normal file
@@ -0,0 +1,20 @@
|
||||
import { View } from "react-native";
|
||||
import TextCustom from "../Text/TextCustom";
|
||||
|
||||
// Komponen Empty
|
||||
const ListEmptyComponent = ({ search }: { search?: string }) => (
|
||||
<View
|
||||
style={{
|
||||
flex: 1,
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
padding: 20,
|
||||
}}
|
||||
>
|
||||
<TextCustom align="center" color="gray">
|
||||
{search ? "Tidak ada hasil pencarian" : "Tidak ada data"}
|
||||
</TextCustom>
|
||||
</View>
|
||||
);
|
||||
|
||||
export default ListEmptyComponent;
|
||||
11
components/_ShareComponent/ListLoaderFooterComponent.tsx
Normal file
11
components/_ShareComponent/ListLoaderFooterComponent.tsx
Normal file
@@ -0,0 +1,11 @@
|
||||
import { View } from "react-native";
|
||||
import LoaderCustom from "../Loader/LoaderCustom";
|
||||
|
||||
const ListLoaderFooterComponent = () =>(
|
||||
<View style={{ paddingVertical: 16, alignItems: "center" }}>
|
||||
<LoaderCustom />
|
||||
</View>
|
||||
)
|
||||
|
||||
|
||||
export default ListLoaderFooterComponent;
|
||||
21
components/_ShareComponent/ListSkeletonComponent.tsx
Normal file
21
components/_ShareComponent/ListSkeletonComponent.tsx
Normal file
@@ -0,0 +1,21 @@
|
||||
import { View } from "react-native";
|
||||
import StackCustom from "../Stack/StackCustom";
|
||||
import SkeletonCustom from "./SkeletonCustom";
|
||||
|
||||
const ListSkeletonComponent = ({
|
||||
length = 5,
|
||||
height = 100,
|
||||
}: {
|
||||
length?: number;
|
||||
height?: number;
|
||||
}) => (
|
||||
<View style={{ flex: 1 }}>
|
||||
<StackCustom>
|
||||
{Array.from({ length }).map((_, i) => (
|
||||
<SkeletonCustom height={height} key={i} />
|
||||
))}
|
||||
</StackCustom>
|
||||
</View>
|
||||
);
|
||||
|
||||
export default ListSkeletonComponent;
|
||||
188
components/_ShareComponent/NewWrapper.tsx
Normal file
188
components/_ShareComponent/NewWrapper.tsx
Normal file
@@ -0,0 +1,188 @@
|
||||
// @/components/NewWrapper.tsx
|
||||
import { MainColor } from "@/constants/color-palet";
|
||||
import { OS_HEIGHT } from "@/constants/constans-value";
|
||||
import { GStyles } from "@/styles/global-styles";
|
||||
import {
|
||||
ImageBackground,
|
||||
Keyboard,
|
||||
KeyboardAvoidingView,
|
||||
Platform,
|
||||
ScrollView,
|
||||
FlatList,
|
||||
TouchableWithoutFeedback,
|
||||
View,
|
||||
StyleProp,
|
||||
ViewStyle,
|
||||
} from "react-native";
|
||||
import {
|
||||
NativeSafeAreaViewProps,
|
||||
SafeAreaView,
|
||||
} from "react-native-safe-area-context";
|
||||
import type { ScrollViewProps, FlatListProps } from "react-native";
|
||||
|
||||
// --- ✅ Tambahkan refreshControl ke BaseProps ---
|
||||
interface BaseProps {
|
||||
withBackground?: boolean;
|
||||
headerComponent?: React.ReactNode;
|
||||
footerComponent?: React.ReactNode;
|
||||
floatingButton?: React.ReactNode;
|
||||
hideFooter?: boolean;
|
||||
edgesFooter?: NativeSafeAreaViewProps["edges"];
|
||||
style?: StyleProp<ViewStyle>;
|
||||
refreshControl?: ScrollViewProps["refreshControl"]; // ✅ dipakai di kedua mode
|
||||
}
|
||||
|
||||
interface StaticModeProps extends BaseProps {
|
||||
children: React.ReactNode;
|
||||
listData?: never;
|
||||
renderItem?: never;
|
||||
}
|
||||
|
||||
interface ListModeProps extends BaseProps {
|
||||
children?: never;
|
||||
listData?: any[];
|
||||
renderItem?: FlatListProps<any>["renderItem"];
|
||||
onEndReached?: () => void;
|
||||
// ✅ Gunakan tipe yang kompatibel dengan FlatList
|
||||
ListHeaderComponent?: React.ReactElement | null;
|
||||
ListFooterComponent?: React.ReactElement | null;
|
||||
ListEmptyComponent?: React.ReactElement | null;
|
||||
keyExtractor?: FlatListProps<any>["keyExtractor"];
|
||||
}
|
||||
|
||||
type NewWrapperProps = StaticModeProps | ListModeProps;
|
||||
|
||||
const NewWrapper = (props: NewWrapperProps) => {
|
||||
const {
|
||||
withBackground = false,
|
||||
headerComponent,
|
||||
footerComponent,
|
||||
floatingButton,
|
||||
hideFooter = false,
|
||||
edgesFooter = [],
|
||||
style,
|
||||
refreshControl, // ✅ sekarang ada di BaseProps
|
||||
} = props;
|
||||
|
||||
const assetBackground = require("../../assets/images/main-background.png");
|
||||
|
||||
const renderContainer = (content: React.ReactNode) => {
|
||||
if (withBackground) {
|
||||
return (
|
||||
<ImageBackground
|
||||
source={assetBackground}
|
||||
resizeMode="cover"
|
||||
style={GStyles.imageBackground}
|
||||
>
|
||||
<View style={[GStyles.containerWithBackground, style]}>
|
||||
{content}
|
||||
</View>
|
||||
</ImageBackground>
|
||||
);
|
||||
}
|
||||
return <View style={[GStyles.container, style]}>{content}</View>;
|
||||
};
|
||||
|
||||
// 🔹 Mode Dinamis
|
||||
if ("listData" in props) {
|
||||
const listProps = props as ListModeProps;
|
||||
|
||||
return (
|
||||
<KeyboardAvoidingView
|
||||
behavior={Platform.OS === "ios" ? "padding" : "height"}
|
||||
style={{ flex: 1, backgroundColor: MainColor.darkblue }}
|
||||
>
|
||||
{headerComponent && (
|
||||
<View style={GStyles.stickyHeader}>{headerComponent}</View>
|
||||
)}
|
||||
<View style={[GStyles.container, style]}>
|
||||
<FlatList
|
||||
data={listProps.listData}
|
||||
renderItem={listProps.renderItem}
|
||||
keyExtractor={
|
||||
listProps.keyExtractor ||
|
||||
((item) => {
|
||||
if (item.id == null) {
|
||||
console.warn("Item tanpa 'id':", item);
|
||||
return `fallback-${JSON.stringify(item)}`;
|
||||
}
|
||||
return String(item.id);
|
||||
})
|
||||
}
|
||||
|
||||
refreshControl={refreshControl} // ✅ dari BaseProps
|
||||
onEndReached={listProps.onEndReached}
|
||||
onEndReachedThreshold={0.5}
|
||||
ListHeaderComponent={listProps.ListHeaderComponent}
|
||||
ListFooterComponent={listProps.ListFooterComponent}
|
||||
ListEmptyComponent={listProps.ListEmptyComponent}
|
||||
contentContainerStyle={{ flexGrow: 1 }}
|
||||
keyboardShouldPersistTaps="handled"
|
||||
/>
|
||||
</View>
|
||||
|
||||
{footerComponent ? (
|
||||
<SafeAreaView
|
||||
edges={Platform.OS === "ios" ? edgesFooter : ["bottom"]}
|
||||
style={{ backgroundColor: MainColor.darkblue, height: OS_HEIGHT }}
|
||||
>
|
||||
{footerComponent}
|
||||
</SafeAreaView>
|
||||
) : hideFooter ? null : (
|
||||
<SafeAreaView
|
||||
edges={["bottom"]}
|
||||
style={{ backgroundColor: MainColor.darkblue }}
|
||||
/>
|
||||
)}
|
||||
|
||||
{floatingButton && (
|
||||
<View style={GStyles.floatingContainer}>{floatingButton}</View>
|
||||
)}
|
||||
</KeyboardAvoidingView>
|
||||
);
|
||||
}
|
||||
|
||||
// 🔹 Mode Statis
|
||||
const staticProps = props as StaticModeProps;
|
||||
|
||||
return (
|
||||
<KeyboardAvoidingView
|
||||
behavior={Platform.OS === "ios" ? "padding" : "height"}
|
||||
style={{ flex: 1, backgroundColor: MainColor.darkblue }}
|
||||
>
|
||||
{headerComponent && (
|
||||
<View style={GStyles.stickyHeader}>{headerComponent}</View>
|
||||
)}
|
||||
|
||||
<ScrollView
|
||||
contentContainerStyle={{ flexGrow: 1 }}
|
||||
keyboardShouldPersistTaps="handled"
|
||||
refreshControl={refreshControl} // ✅ sekarang valid
|
||||
>
|
||||
<TouchableWithoutFeedback onPress={Keyboard.dismiss}>
|
||||
{renderContainer(staticProps.children)}
|
||||
</TouchableWithoutFeedback>
|
||||
</ScrollView>
|
||||
|
||||
{footerComponent ? (
|
||||
<SafeAreaView
|
||||
edges={Platform.OS === "ios" ? edgesFooter : ["bottom"]}
|
||||
style={{ backgroundColor: MainColor.darkblue, height: OS_HEIGHT }}
|
||||
>
|
||||
{footerComponent}
|
||||
</SafeAreaView>
|
||||
) : hideFooter ? null : (
|
||||
<SafeAreaView
|
||||
edges={["bottom"]}
|
||||
style={{ backgroundColor: MainColor.darkblue }}
|
||||
/>
|
||||
)}
|
||||
|
||||
{floatingButton && (
|
||||
<View style={GStyles.floatingContainer}>{floatingButton}</View>
|
||||
)}
|
||||
</KeyboardAvoidingView>
|
||||
);
|
||||
};
|
||||
|
||||
export default NewWrapper;
|
||||
59
components/_ShareComponent/SkeletonCustom.tsx
Normal file
59
components/_ShareComponent/SkeletonCustom.tsx
Normal file
@@ -0,0 +1,59 @@
|
||||
// components/CustomSkeleton.tsx
|
||||
import React from "react";
|
||||
import { View, StyleProp, ViewStyle, DimensionValue } from "react-native";
|
||||
import { MotiView } from "moti";
|
||||
import { AccentColor, MainColor } from "@/constants/color-palet";
|
||||
|
||||
interface CustomSkeletonProps {
|
||||
isLoading?: boolean;
|
||||
style?: StyleProp<ViewStyle>;
|
||||
width?: DimensionValue;
|
||||
height?: DimensionValue;
|
||||
radius?: number;
|
||||
}
|
||||
|
||||
const CustomSkeleton: React.FC<CustomSkeletonProps> = ({
|
||||
isLoading = true,
|
||||
style,
|
||||
width = "100%",
|
||||
height = 16,
|
||||
radius = 8,
|
||||
}) => {
|
||||
if (!isLoading) return null;
|
||||
|
||||
return (
|
||||
<View
|
||||
style={[
|
||||
{
|
||||
width,
|
||||
height,
|
||||
borderRadius: radius,
|
||||
backgroundColor: AccentColor.darkblue,
|
||||
overflow: "hidden",
|
||||
position: "relative",
|
||||
},
|
||||
style,
|
||||
]}
|
||||
>
|
||||
<MotiView
|
||||
from={{ translateY: -100 }}
|
||||
animate={{ translateY: 100 }}
|
||||
transition={{
|
||||
duration: 1200,
|
||||
repeat: Infinity,
|
||||
type: "timing",
|
||||
}}
|
||||
style={{
|
||||
position: "absolute",
|
||||
left: 0,
|
||||
right: 0,
|
||||
height: 100,
|
||||
backgroundColor: MainColor.soft_darkblue,
|
||||
borderRadius: 4,
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default CustomSkeleton;
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
View,
|
||||
StyleProp,
|
||||
ViewStyle,
|
||||
ScrollViewProps,
|
||||
} from "react-native";
|
||||
import { NativeSafeAreaViewProps, SafeAreaView } from "react-native-safe-area-context";
|
||||
|
||||
@@ -23,6 +24,7 @@ interface ViewWrapperProps {
|
||||
hideFooter?: boolean;
|
||||
edgesFooter?: NativeSafeAreaViewProps["edges"];
|
||||
style?: StyleProp<ViewStyle>;
|
||||
refreshControl?: ScrollViewProps["refreshControl"];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -40,6 +42,7 @@ const ViewWrapper = ({
|
||||
hideFooter = false,
|
||||
edgesFooter =[],
|
||||
style,
|
||||
refreshControl,
|
||||
}: ViewWrapperProps) => {
|
||||
const assetBackground = require("../../assets/images/main-background.png");
|
||||
|
||||
@@ -57,6 +60,7 @@ const ViewWrapper = ({
|
||||
<ScrollView
|
||||
contentContainerStyle={{ flexGrow: 1 }}
|
||||
keyboardShouldPersistTaps="handled"
|
||||
refreshControl={refreshControl}
|
||||
>
|
||||
<TouchableWithoutFeedback onPress={Keyboard.dismiss}>
|
||||
<View style={{ flex: 1 }}>
|
||||
|
||||
@@ -59,6 +59,7 @@ import ViewWrapper from "./_ShareComponent/ViewWrapper";
|
||||
import SearchInput from "./_ShareComponent/SearchInput";
|
||||
import DummyLandscapeImage from "./_ShareComponent/DummyLandscapeImage";
|
||||
import GridComponentView from "./_ShareComponent/GridSectionView";
|
||||
import NewWrapper from "./_ShareComponent/NewWrapper";
|
||||
// Progress
|
||||
import ProgressCustom from "./Progress/ProgressCustom";
|
||||
// Loader
|
||||
@@ -119,6 +120,7 @@ export {
|
||||
DummyLandscapeImage,
|
||||
GridComponentView,
|
||||
Spacing,
|
||||
NewWrapper,
|
||||
// Stack
|
||||
StackCustom,
|
||||
TabBarBackground,
|
||||
|
||||
@@ -45,3 +45,23 @@ export const AdminColor = {
|
||||
// Warna Asli: #002e59
|
||||
// Warna Lebih Gelap: #001f3b
|
||||
// Warna Tergelap: #001323
|
||||
|
||||
|
||||
export const PlaceholderColor = {
|
||||
light: {
|
||||
text: "#000",
|
||||
placeholder: "#666",
|
||||
border: "#ccc",
|
||||
background: "#fff",
|
||||
error: "#d00",
|
||||
icon: "#555",
|
||||
},
|
||||
dark: {
|
||||
text: "#fff",
|
||||
placeholder: "#aaa",
|
||||
border: "#444",
|
||||
background: "#1a1a1a",
|
||||
error: "#ff4d4d",
|
||||
icon: "#ccc",
|
||||
},
|
||||
};
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
apiRegister,
|
||||
apiValidationCode,
|
||||
} from "@/service/api-config";
|
||||
import { apiDeviceTokenDeleted } from "@/service/api-device-token";
|
||||
import { IUser } from "@/types/User";
|
||||
import AsyncStorage from "@react-native-async-storage/async-storage";
|
||||
import { router } from "expo-router";
|
||||
@@ -24,6 +25,7 @@ type AuthContextType = {
|
||||
registerUser: (userData: {
|
||||
username: string;
|
||||
nomor: string;
|
||||
termsOfServiceAccepted: boolean;
|
||||
}) => Promise<void>;
|
||||
userData: (token: string) => Promise<any>;
|
||||
};
|
||||
@@ -71,8 +73,31 @@ export const AuthProvider = ({ children }: { children: React.ReactNode }) => {
|
||||
const loginWithNomor = async (nomor: string) => {
|
||||
setIsLoading(true);
|
||||
try {
|
||||
console.log("[Masuk provider]", nomor);
|
||||
const response = await apiLogin({ nomor: nomor });
|
||||
await AsyncStorage.setItem("kode_otp", response.kodeId);
|
||||
console.log("[RESPONSE AUTH]", JSON.stringify(response));
|
||||
|
||||
|
||||
if (response.success) {
|
||||
console.log("[Keluar provider]", nomor);
|
||||
Toast.show({
|
||||
type: "success",
|
||||
text1: "Sukses",
|
||||
text2: "Kode OTP berhasil dikirim",
|
||||
});
|
||||
|
||||
await AsyncStorage.setItem("kode_otp", response.kodeId);
|
||||
router.push(`/verification?nomor=${nomor}`);
|
||||
return;
|
||||
} else {
|
||||
router.push(`/register?nomor=${nomor}`);
|
||||
Toast.show({
|
||||
type: "info",
|
||||
text1: "Info",
|
||||
text2: "Silahkan mendaftar",
|
||||
});
|
||||
return;
|
||||
}
|
||||
} catch (error: any) {
|
||||
throw new Error(error.response?.data?.message || "Gagal kirim OTP");
|
||||
} finally {
|
||||
@@ -80,13 +105,26 @@ export const AuthProvider = ({ children }: { children: React.ReactNode }) => {
|
||||
}
|
||||
};
|
||||
|
||||
// const loginWithNomor = async (nomor: string) => {
|
||||
// setIsLoading(true);
|
||||
// try {
|
||||
// const response = await apiLogin({ nomor: nomor });
|
||||
// await AsyncStorage.setItem("kode_otp", response.kodeId);
|
||||
// } catch (error: any) {
|
||||
// throw new Error(error.response?.data?.message || "Gagal kirim OTP");
|
||||
// } finally {
|
||||
// setIsLoading(false);
|
||||
// }
|
||||
// };
|
||||
|
||||
// --- 2. Validasi OTP & cek user ---
|
||||
const validateOtp = async (nomor: string) => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
const response = await apiValidationCode({ nomor: nomor });
|
||||
|
||||
const { token } = response;
|
||||
console.log("[RESPONSE VALIDASI OTP]", JSON.stringify(response, null, 2));
|
||||
|
||||
if (response.success) {
|
||||
setToken(token);
|
||||
await AsyncStorage.setItem("authToken", token);
|
||||
@@ -102,21 +140,26 @@ export const AuthProvider = ({ children }: { children: React.ReactNode }) => {
|
||||
await AsyncStorage.setItem("userData", JSON.stringify(dataUser));
|
||||
|
||||
if (response.active) {
|
||||
if (response.roleId === "1") {
|
||||
return "/(application)/(user)/home";
|
||||
} else {
|
||||
return "/(application)/admin/dashboard";
|
||||
}
|
||||
// if (response.roleId === "1") {
|
||||
// router.replace("/(application)/(user)/home");
|
||||
// return;
|
||||
// } else {
|
||||
// router.replace("/(application)/admin/dashboard");
|
||||
// return;
|
||||
// }
|
||||
router.replace("/(application)/(user)/home");
|
||||
return
|
||||
} else {
|
||||
return "/(application)/(user)/waiting-room";
|
||||
router.replace("/(application)/(user)/waiting-room");
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
Toast.show({
|
||||
type: "info",
|
||||
text1: "Anda belum terdaftar",
|
||||
text2: "Silahkan daftar terlebih dahulu",
|
||||
text1: "Terjadi kesalahan",
|
||||
text2: "Silahkan coba lagi",
|
||||
});
|
||||
return `/register?nomor=${nomor}`;
|
||||
return;
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.log("Error validasi otp >>", (error as Error).message || error);
|
||||
@@ -131,6 +174,10 @@ export const AuthProvider = ({ children }: { children: React.ReactNode }) => {
|
||||
// --- 3. Ambil data user ---
|
||||
const userData = async (token: string) => {
|
||||
try {
|
||||
if (!token) {
|
||||
throw new Error("Token tidak ditemukan");
|
||||
}
|
||||
|
||||
setIsLoading(true);
|
||||
const response = await apiConfig.get(`/mobile?token=${token}`, {
|
||||
headers: {
|
||||
@@ -144,7 +191,10 @@ export const AuthProvider = ({ children }: { children: React.ReactNode }) => {
|
||||
await AsyncStorage.setItem("userData", JSON.stringify(dataUser));
|
||||
return dataUser;
|
||||
} catch (error: any) {
|
||||
console.log(error.response?.data?.message + "user" || "Gagal mengambil data user");
|
||||
console.log(
|
||||
"[LOAD USER DATA]",
|
||||
error.response?.data?.message + "user" || "Gagal mengambil data user"
|
||||
);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
@@ -154,12 +204,13 @@ export const AuthProvider = ({ children }: { children: React.ReactNode }) => {
|
||||
const registerUser = async (userData: {
|
||||
username: string;
|
||||
nomor: string;
|
||||
termsOfServiceAccepted: boolean;
|
||||
}) => {
|
||||
setIsLoading(true);
|
||||
try {
|
||||
const response = await apiRegister({ data: userData });
|
||||
console.log("[REGISTER FETCH]", JSON.stringify(response, null, 2));
|
||||
|
||||
const { token } = response;
|
||||
if (!response.success) {
|
||||
Toast.show({
|
||||
type: "info",
|
||||
@@ -170,23 +221,63 @@ export const AuthProvider = ({ children }: { children: React.ReactNode }) => {
|
||||
return;
|
||||
}
|
||||
|
||||
setToken(token);
|
||||
await AsyncStorage.setItem("authToken", token);
|
||||
Toast.show({
|
||||
type: "success",
|
||||
text1: "Sukses",
|
||||
text2: "Anda berhasil terdaftar",
|
||||
});
|
||||
router.replace("/(application)/(user)/waiting-room");
|
||||
router.replace(`/verification?nomor=${userData.nomor}`);
|
||||
return;
|
||||
} catch (error: any) {
|
||||
Toast.show({
|
||||
type: "error",
|
||||
text1: "Error",
|
||||
text2: error.response?.data?.message || "Gagal mendaftar",
|
||||
});
|
||||
console.log("Error register", error);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
// const registerUser = async (userData: {
|
||||
// username: string;
|
||||
// nomor: string;
|
||||
// termsOfServiceAccepted: boolean;
|
||||
// }) => {
|
||||
// setIsLoading(true);
|
||||
// try {
|
||||
// const response = await apiRegister({ data: userData });
|
||||
// console.log("response", response);
|
||||
|
||||
// const { token } = response;
|
||||
// if (!response.success) {
|
||||
// Toast.show({
|
||||
// type: "info",
|
||||
// text1: "Info",
|
||||
// text2: response.message,
|
||||
// });
|
||||
|
||||
// return;
|
||||
// }
|
||||
|
||||
// setToken(token);
|
||||
// await AsyncStorage.setItem("authToken", token);
|
||||
// Toast.show({
|
||||
// type: "success",
|
||||
// text1: "Sukses",
|
||||
// text2: "Anda berhasil terdaftar",
|
||||
// });
|
||||
// router.replace("/(application)/(user)/waiting-room");
|
||||
// return;
|
||||
// } catch (error: any) {
|
||||
// console.log("Error register", error);
|
||||
// } finally {
|
||||
// setIsLoading(false);
|
||||
// }
|
||||
// };
|
||||
|
||||
// --- 5. Logout ---
|
||||
|
||||
const logout = async () => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
@@ -194,7 +285,7 @@ export const AuthProvider = ({ children }: { children: React.ReactNode }) => {
|
||||
setUser(null);
|
||||
await AsyncStorage.removeItem("authToken");
|
||||
await AsyncStorage.removeItem("userData");
|
||||
setIsLoading(false);
|
||||
await apiDeviceTokenDeleted({userId: user?.id as any})
|
||||
|
||||
Toast.show({
|
||||
type: "success",
|
||||
|
||||
27
hooks/use-foreground-notifications.ts
Normal file
27
hooks/use-foreground-notifications.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { useEffect } from "react";
|
||||
import {
|
||||
getMessaging,
|
||||
onMessage,
|
||||
FirebaseMessagingTypes,
|
||||
} from "@react-native-firebase/messaging";
|
||||
|
||||
// Gunakan tipe resmi dari library
|
||||
type RemoteMessage = FirebaseMessagingTypes.RemoteMessage;
|
||||
|
||||
export function useForegroundNotifications(
|
||||
onMessageReceived: (message: RemoteMessage) => void
|
||||
) {
|
||||
useEffect(() => {
|
||||
const messaging = getMessaging();
|
||||
|
||||
const unsubscribe = onMessage(messaging, (remoteMessage) => {
|
||||
console.log(
|
||||
"🔔 Notifikasi diterima saat app aktif:",
|
||||
JSON.stringify(remoteMessage, null, 2)
|
||||
);
|
||||
onMessageReceived(remoteMessage);
|
||||
});
|
||||
|
||||
return unsubscribe;
|
||||
}, [onMessageReceived]);
|
||||
}
|
||||
131
hooks/use-notification-store.tsx
Normal file
131
hooks/use-notification-store.tsx
Normal file
@@ -0,0 +1,131 @@
|
||||
// hooks/useNotificationStore.ts
|
||||
import {
|
||||
createContext,
|
||||
ReactNode,
|
||||
useContext,
|
||||
useEffect,
|
||||
useState,
|
||||
} from "react";
|
||||
import { useAuth } from "./use-auth";
|
||||
import {
|
||||
apiGetNotificationsById,
|
||||
apiNotificationUnreadCount,
|
||||
} from "@/service/api-notifications";
|
||||
|
||||
type AppNotification = {
|
||||
id: string;
|
||||
title: string;
|
||||
body: string;
|
||||
data?: Record<string, string>;
|
||||
isRead: boolean;
|
||||
timestamp: number;
|
||||
type: "notification" | "trigger";
|
||||
// untuk id dari setiap kategori app
|
||||
appId?: string;
|
||||
kategoriApp?:
|
||||
| "JOB"
|
||||
| "VOTING"
|
||||
| "EVENT"
|
||||
| "DONASI"
|
||||
| "INVESTASI"
|
||||
| "COLLABORATION"
|
||||
| "FORUM"
|
||||
| "ACCESS"; // Untuk trigger akses user;
|
||||
};
|
||||
|
||||
type NotificationContextType = {
|
||||
notifications: AppNotification[];
|
||||
unreadCount: number;
|
||||
addNotification: (
|
||||
notif: Omit<AppNotification, "id" | "isRead" | "timestamp">
|
||||
) => void;
|
||||
markAsRead: (id: string) => void;
|
||||
syncUnreadCount: () => Promise<void>;
|
||||
};
|
||||
|
||||
const NotificationContext = createContext<NotificationContextType>({
|
||||
notifications: [],
|
||||
unreadCount: 0,
|
||||
addNotification: () => {},
|
||||
markAsRead: () => {},
|
||||
syncUnreadCount: async () => {},
|
||||
});
|
||||
|
||||
export const NotificationProvider = ({ children }: { children: ReactNode }) => {
|
||||
const { user } = useAuth();
|
||||
const [notifications, setNotifications] = useState<AppNotification[]>([]);
|
||||
const [unreadCount, setUnreadCount] = useState(0);
|
||||
|
||||
console.log(
|
||||
"🚀 Notifications Masuk:",
|
||||
JSON.stringify(notifications, null, 2)
|
||||
);
|
||||
|
||||
// Sync unread count dari backend saat provider di-mount
|
||||
useEffect(() => {
|
||||
fetchUnreadCount();
|
||||
}, [user?.id]);
|
||||
|
||||
const fetchUnreadCount = async () => {
|
||||
try {
|
||||
const count = await apiNotificationUnreadCount({
|
||||
id: user?.id as any,
|
||||
}); // ← harus return number
|
||||
const result = count.data;
|
||||
console.log("📖 Unread count:", result);
|
||||
setUnreadCount(result);
|
||||
} catch (error) {
|
||||
console.error("Gagal fetch unread count:", error);
|
||||
}
|
||||
};
|
||||
|
||||
const addNotification = (
|
||||
notif: Omit<AppNotification, "id" | "isRead" | "timestamp">
|
||||
) => {
|
||||
setNotifications((prev) => [
|
||||
{
|
||||
...notif,
|
||||
id: Date.now().toString(),
|
||||
isRead: false,
|
||||
timestamp: Date.now(),
|
||||
},
|
||||
...prev,
|
||||
]);
|
||||
|
||||
setUnreadCount((prev) => prev + 1);
|
||||
};
|
||||
|
||||
const markAsRead = (id: string) => {
|
||||
setNotifications((prev) =>
|
||||
prev.map((n) => (n.id === id ? { ...n, isRead: true } : n))
|
||||
);
|
||||
};
|
||||
|
||||
const syncUnreadCount = async () => {
|
||||
try {
|
||||
const count = await apiNotificationUnreadCount({
|
||||
id: user?.id as any,
|
||||
}); // ← harus return number
|
||||
const result = count.data;
|
||||
setUnreadCount(result);
|
||||
} catch (error) {
|
||||
console.warn("⚠️ Gagal sync unread count:", error);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<NotificationContext.Provider
|
||||
value={{
|
||||
notifications,
|
||||
addNotification,
|
||||
markAsRead,
|
||||
unreadCount,
|
||||
syncUnreadCount,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</NotificationContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export const useNotificationStore = () => useContext(NotificationContext);
|
||||
113
hooks/use-notification-store.tsx.back
Normal file
113
hooks/use-notification-store.tsx.back
Normal file
@@ -0,0 +1,113 @@
|
||||
// hooks/useNotificationStore.ts
|
||||
import { apiGetNotificationsById } from "@/service/api-notifications";
|
||||
import { createContext, ReactNode, useContext, useState, useEffect } from "react";
|
||||
import { useAuth } from "./use-auth";
|
||||
|
||||
type AppNotification = {
|
||||
id: string;
|
||||
title: string;
|
||||
body: string;
|
||||
data?: Record<string, string>;
|
||||
isRead: boolean;
|
||||
timestamp: number;
|
||||
type: "notification" | "trigger";
|
||||
appId?: string;
|
||||
kategoriApp?:
|
||||
| "JOB"
|
||||
| "VOTING"
|
||||
| "EVENT"
|
||||
| "DONASI"
|
||||
| "INVESTASI"
|
||||
| "COLLABORATION"
|
||||
| "FORUM"
|
||||
| "ACCESS";
|
||||
};
|
||||
|
||||
type NotificationContextType = {
|
||||
notifications: AppNotification[];
|
||||
unreadCount: number;
|
||||
addNotification: (
|
||||
notif: Omit<AppNotification, "id" | "isRead" | "timestamp">
|
||||
) => void;
|
||||
markAsRead: (id: string) => void;
|
||||
syncUnreadCount: () => Promise<void>;
|
||||
};
|
||||
|
||||
const NotificationContext = createContext<NotificationContextType>({
|
||||
notifications: [],
|
||||
unreadCount: 0,
|
||||
addNotification: () => {},
|
||||
markAsRead: () => {},
|
||||
syncUnreadCount: async () => {},
|
||||
});
|
||||
|
||||
export const NotificationProvider = ({ children }: { children: ReactNode }) => {
|
||||
const {user} = useAuth()
|
||||
const [notifications, setNotifications] = useState<AppNotification[]>([]);
|
||||
const [unreadCount, setUnreadCount] = useState(0);
|
||||
|
||||
// 🔔 Sync unread count dari backend saat provider di-mount
|
||||
useEffect(() => {
|
||||
const fetchUnreadCount = async () => {
|
||||
try {
|
||||
const count = await apiGetNotificationsById({
|
||||
id: user?.id as any,
|
||||
category: "count-as-unread"
|
||||
}); // ← harus return number
|
||||
const result = count.data
|
||||
setUnreadCount(result);
|
||||
} catch (error) {
|
||||
console.erro("⚠️ Gagal fetch unread count:", error);
|
||||
}
|
||||
};
|
||||
|
||||
fetchUnreadCount();
|
||||
}, []);
|
||||
|
||||
const addNotification = (
|
||||
notif: Omit<AppNotification, "id" | "isRead" | "timestamp">
|
||||
) => {
|
||||
setNotifications((prev) => [
|
||||
{
|
||||
...notif,
|
||||
id: Date.now().toString(),
|
||||
isRead: false,
|
||||
timestamp: Date.now(),
|
||||
},
|
||||
...prev,
|
||||
]);
|
||||
// Tambahkan ke unread count (untuk notifikasi foreground)
|
||||
setUnreadCount((prev) => prev + 1);
|
||||
};
|
||||
|
||||
const markAsRead = (id: string) => {
|
||||
setNotifications((prev) =>
|
||||
prev.map((n) => (n.id === id ? { ...n, isRead: true } : n))
|
||||
);
|
||||
// Kurangi unread count
|
||||
setUnreadCount((prev) => Math.max(0, prev - 1));
|
||||
};
|
||||
|
||||
const syncUnreadCount = async () => {
|
||||
try {
|
||||
const count = await apiGetNotificationsById({
|
||||
id: user?.id as any,
|
||||
category: "count-as-unread"
|
||||
}); // ← harus return number
|
||||
const result = count.data
|
||||
setUnreadCount(result);
|
||||
} catch (error) {
|
||||
console.warn("⚠️ Gagal sync unread count:", error);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<NotificationContext.Provider
|
||||
value={{ notifications, unreadCount, addNotification, markAsRead, syncUnreadCount }}
|
||||
>
|
||||
{children}
|
||||
</NotificationContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export const useNotificationStore = () => useContext(NotificationContext);
|
||||
97
hooks/use-paginated-api.ts
Normal file
97
hooks/use-paginated-api.ts
Normal file
@@ -0,0 +1,97 @@
|
||||
// @/hooks/use-paginated-api.ts
|
||||
import { useCallback, useState, useEffect, useRef } from "react";
|
||||
|
||||
interface UsePaginatedApiProps {
|
||||
fetcher: (params: { page: number; search?: string }) => Promise<any[]>;
|
||||
initialSearch?: string; // mengatur nilai awal dari state
|
||||
pageSize?: number;
|
||||
dependencies?: any[]; // untuk refresh saat deps berubah (misal: user.id)
|
||||
}
|
||||
|
||||
export const usePaginatedApi = ({
|
||||
fetcher,
|
||||
initialSearch = "",
|
||||
pageSize = 5,
|
||||
dependencies = [],
|
||||
}: UsePaginatedApiProps) => {
|
||||
const [data, setData] = useState<any[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [refreshing, setRefreshing] = useState(false);
|
||||
const [hasMore, setHasMore] = useState(true);
|
||||
const [page, setPage] = useState(1);
|
||||
const [search, setSearch] = useState(initialSearch);
|
||||
const refreshingRef = useRef<boolean>(false);
|
||||
const loadingRef = useRef<boolean>(false);
|
||||
const fetchRef = useRef(false);
|
||||
|
||||
const fetchData = useCallback(
|
||||
async (pageNumber: number, clear: boolean) => {
|
||||
const isRefresh = clear;
|
||||
|
||||
// 🔒 Proteksi: jangan jalankan jika sedang refreshing (untuk refresh)
|
||||
if (isRefresh && refreshingRef.current) return;
|
||||
// 🔒 Proteksi: jangan jalankan loadMore jika sedang loading
|
||||
if (!isRefresh && loadingRef.current) return;
|
||||
|
||||
const setLoadingState = (isLoading: boolean) => {
|
||||
if (isRefresh) {
|
||||
setRefreshing(isLoading);
|
||||
refreshingRef.current = isLoading;
|
||||
} else {
|
||||
setLoading(isLoading);
|
||||
loadingRef.current = isLoading;
|
||||
}
|
||||
};
|
||||
|
||||
setLoadingState(true);
|
||||
|
||||
try {
|
||||
const newData = await fetcher({ page: pageNumber, search });
|
||||
setData((prev) => {
|
||||
const current = Array.isArray(prev) ? prev : [];
|
||||
return clear ? newData : [...current, ...newData];
|
||||
});
|
||||
setHasMore(newData.length === pageSize);
|
||||
setPage(pageNumber);
|
||||
} catch (error) {
|
||||
console.error("[usePaginatedApi] Error:", error);
|
||||
setHasMore(false);
|
||||
} finally {
|
||||
setLoadingState(false);
|
||||
}
|
||||
},
|
||||
[search, hasMore, pageSize, ...dependencies]
|
||||
);
|
||||
|
||||
const onRefresh = useCallback(() => {
|
||||
fetchData(1, true);
|
||||
}, [fetchData]);
|
||||
|
||||
const loadMore = useCallback(() => {
|
||||
if (hasMore && !loading && !refreshing) {
|
||||
fetchData(page + 1, false);
|
||||
}
|
||||
}, [hasMore, loading, refreshing, page, fetchData]);
|
||||
|
||||
// Reset & fetch ulang saat search atau deps berubah
|
||||
useEffect(() => {
|
||||
if (fetchRef.current) return; // hindari double initial
|
||||
fetchRef.current = true;
|
||||
|
||||
setPage(1);
|
||||
setData([]);
|
||||
setHasMore(true);
|
||||
fetchData(1, true);
|
||||
}, [search, ...dependencies]);
|
||||
|
||||
return {
|
||||
data,
|
||||
loading,
|
||||
refreshing,
|
||||
hasMore,
|
||||
search,
|
||||
setSearch,
|
||||
onRefresh,
|
||||
loadMore,
|
||||
};
|
||||
};
|
||||
File diff suppressed because one or more lines are too long
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user