Merge pull request 'amalia/25-sept-25' (#42) from amalia/25-sept-25 into join

Reviewed-on: bip/mobile-darmasaba#42
This commit is contained in:
2025-09-26 10:22:11 +08:00
10 changed files with 160 additions and 74 deletions

View File

@@ -153,14 +153,14 @@ export default function DetailDiscussionGeneral() {
<MaterialIcons name="chat" size={25} color={'#384288'} />
</View>
}
title={data?.title}
title={data?.title + " " + data?.createdAt}
subtitle={
!data?.isActive ?
<LabelStatus category='warning' text='ARSIP' size="small" />
:
<LabelStatus category={data.status == 1 ? 'success' : 'error'} text={data.status == 1 ? 'BUKA' : 'TUTUP'} size="small" />
}
rightTopInfo={data?.createdAt}
// rightTopInfo={data?.createdAt}
desc={data?.desc}
leftBottomInfo={
<View style={[Styles.rowItemsCenter]}>
@@ -168,6 +168,11 @@ export default function DetailDiscussionGeneral() {
<Text style={[Styles.textInformation, Styles.cGray, Styles.mb05]}>{dataKomentar.length} Komentar</Text>
</View>
}
rightBottomInfo={
<View style={[Styles.rowItemsCenter]}>
<Text style={[Styles.textInformation, Styles.cGray, Styles.mb05]}>{data?.createdAt}</Text>
</View>
}
/>
}
<View style={[Styles.p15]}>
@@ -210,7 +215,7 @@ export default function DetailDiscussionGeneral() {
disable={(data?.status === 2 || !data?.isActive || (!memberDiscussion && (entityUser.role == "user" || entityUser.role == "coadmin")))}
type="default"
round
placeholder="Kirim Komentar2"
placeholder="Kirim Komentar"
bg="white"
onChange={setKomentar}
value={komentar}

View File

@@ -98,22 +98,26 @@ export default function Discussion() {
return (
<View style={[Styles.p15, { flex: 1 }]}>
<View>
<View style={[Styles.wrapBtnTab]}>
<ButtonTab
active={status == "false" ? "false" : "true"}
value="true"
onPress={() => { setStatus("true") }}
label="Aktif"
icon={<Feather name="check-circle" color={status == "false" ? 'black' : 'white'} size={20} />}
n={2} />
<ButtonTab
active={status == "false" ? "false" : "true"}
value="false"
onPress={() => { setStatus("false") }}
label="Arsip"
icon={<AntDesign name="closecircleo" color={status == "true" ? 'black' : 'white'} size={20} />}
n={2} />
</View>
{
entityUser.role != "user" && entityUser.role != "coadmin" &&
<View style={[Styles.wrapBtnTab]}>
<ButtonTab
active={status == "false" ? "false" : "true"}
value="true"
onPress={() => { setStatus("true") }}
label="Aktif"
icon={<Feather name="check-circle" color={status == "false" ? 'black' : 'white'} size={20} />}
n={2} />
<ButtonTab
active={status == "false" ? "false" : "true"}
value="false"
onPress={() => { setStatus("false") }}
label="Arsip"
icon={<AntDesign name="closecircleo" color={status == "true" ? 'black' : 'white'} size={20} />}
n={2} />
</View>
}
<InputSearch onChange={setSearch} />
{
(entityUser.role == "supadmin" || entityUser.role == "developer") &&

View File

@@ -135,22 +135,25 @@ export default function MemberDiscussionDetail() {
router.push(`/member/${chooseUser.idUser}`)
}}
/>
{
entityUser.role != "user" && entityUser.role != "coadmin" &&
<MenuItemRow
icon={<MaterialCommunityIcons name="account-remove" color="black" size={25} />}
title="Keluarkan"
onPress={() => {
setModal(false)
AlertKonfirmasi({
title: 'Konfirmasi',
desc: 'Apakah Anda yakin ingin mengeluarkan anggota?',
onPress: () => {
handleDeleteUser()
}
})
<MenuItemRow
icon={<MaterialCommunityIcons name="account-remove" color="black" size={25} />}
title="Keluarkan"
onPress={() => {
setModal(false)
AlertKonfirmasi({
title: 'Konfirmasi',
desc: 'Apakah Anda yakin ingin mengeluarkan anggota?',
onPress: () => {
handleDeleteUser()
}
})
}}
/>
}
}}
/>
</View>
</DrawerBottom>
</SafeAreaView>

View File

@@ -7,7 +7,7 @@ import SkeletonContent from "@/components/skeletonContent";
import Text from "@/components/Text";
import { ConstEnv } from "@/constants/ConstEnv";
import Styles from "@/constants/Styles";
import { apiGetDiscussion } from "@/lib/api";
import { apiGetDiscussion, apiGetDivisionOneFeature } from "@/lib/api";
import { useAuthSession } from "@/providers/AuthProvider";
import { AntDesign, Feather, Ionicons } from "@expo/vector-icons";
import { router, useLocalSearchParams } from "expo-router";
@@ -41,6 +41,30 @@ export default function DiscussionDivision() {
const [waiting, setWaiting] = useState(false)
const [status, setStatus] = useState<'true' | 'false'>('true')
const [refreshing, setRefreshing] = useState(false)
const [isMemberDivision, setIsMemberDivision] = useState(false)
const [isAdminDivision, setIsAdminDivision] = useState(false)
const entityUser = useSelector((state: any) => state.user)
async function handleCheckMember() {
try {
const hasil = await decryptToken(String(token?.current));
const response = await apiGetDivisionOneFeature({
id,
user: hasil,
cat: "check-member",
});
const response2 = await apiGetDivisionOneFeature({
id,
user: hasil,
cat: "check-admin",
});
setIsMemberDivision(response.data);
setIsAdminDivision(response2.data);
} catch (error) {
console.error(error);
}
}
async function handleLoad(loading: boolean, thisPage: number) {
try {
@@ -80,6 +104,10 @@ export default function DiscussionDivision() {
}, 1000);
}
useEffect(() => {
handleCheckMember()
}, [])
const handleRefresh = async () => {
setRefreshing(true)
handleLoad(false, 1)
@@ -101,25 +129,29 @@ export default function DiscussionDivision() {
return (
<View style={[Styles.p15, { flex: 1 }]}>
<View>
<View style={[Styles.wrapBtnTab]}>
<ButtonTab
active={status == "false" ? "false" : "true"}
value="true"
onPress={() => { setStatus("true") }}
label="Aktif"
icon={<Feather name="check-circle" color={status == "false" ? 'black' : 'white'} size={20} />}
n={2} />
<ButtonTab
active={status == "false" ? "false" : "true"}
value="false"
onPress={() => { setStatus("false") }}
label="Arsip"
icon={<AntDesign name="closecircleo" color={status == "true" ? 'black' : 'white'} size={20} />}
n={2} />
{
((entityUser.role != "user" && entityUser.role != "coadmin") || isAdminDivision) &&
<View>
<View style={[Styles.wrapBtnTab]}>
<ButtonTab
active={status == "false" ? "false" : "true"}
value="true"
onPress={() => { setStatus("true") }}
label="Aktif"
icon={<Feather name="check-circle" color={status == "false" ? 'black' : 'white'} size={20} />}
n={2} />
<ButtonTab
active={status == "false" ? "false" : "true"}
value="false"
onPress={() => { setStatus("false") }}
label="Arsip"
icon={<AntDesign name="closecircleo" color={status == "true" ? 'black' : 'white'} size={20} />}
n={2} />
</View>
<InputSearch onChange={setSearch} />
</View>
<InputSearch onChange={setSearch} />
</View>
}
<View style={[{ flex: 2 }, Styles.mt05]}>
{

View File

@@ -11,7 +11,7 @@ import Text from "@/components/Text"
import { ColorsStatus } from "@/constants/ColorsStatus"
import { ConstEnv } from "@/constants/ConstEnv"
import Styles from "@/constants/Styles"
import { apiDeleteMemberDivision, apiGetDivisionOneDetail, apiUpdateStatusAdminDivision } from "@/lib/api"
import { apiDeleteMemberDivision, apiGetDivisionOneDetail, apiGetDivisionOneFeature, apiUpdateStatusAdminDivision } from "@/lib/api"
import { useAuthSession } from "@/providers/AuthProvider"
import { Feather, MaterialCommunityIcons, MaterialIcons } from "@expo/vector-icons"
import { router, Stack, useLocalSearchParams } from "expo-router"
@@ -39,6 +39,7 @@ type PropsMember = {
}
export default function InformationDivision() {
const entityUser = useSelector((state: any) => state.user)
const { id } = useLocalSearchParams<{ id: string }>()
const [isModal, setModal] = useState(false)
const { token, decryptToken } = useAuthSession()
@@ -48,6 +49,8 @@ export default function InformationDivision() {
const update = useSelector((state: any) => state.divisionUpdate)
const arrSkeleton = Array.from({ length: 5 }, (_, index) => index)
const [loading, setLoading] = useState(true)
const [isMemberDivision, setIsMemberDivision] = useState(false)
const [isAdminDivision, setIsAdminDivision] = useState(false)
const [dataMemberChoose, setDataMemberChoose] = useState({
id: '',
name: '',
@@ -113,12 +116,34 @@ export default function InformationDivision() {
}
}
async function handleCheckMember() {
try {
const hasil = await decryptToken(String(token?.current));
const response = await apiGetDivisionOneFeature({
id,
user: hasil,
cat: "check-member",
});
const response2 = await apiGetDivisionOneFeature({
id,
user: hasil,
cat: "check-admin",
});
setIsMemberDivision(response.data);
setIsAdminDivision(response2.data);
} catch (error) {
console.error(error);
}
}
useEffect(() => {
handleLoad(false)
}, [refresh, update])
useEffect(() => {
handleLoad(true)
handleCheckMember()
}, [])
function handleChooseMember(item: PropsMember) {
@@ -133,7 +158,7 @@ export default function InformationDivision() {
headerLeft: () => <ButtonBackHeader onPress={() => { router.back() }} />,
headerTitle: 'Informasi Divisi',
headerTitleAlign: 'center',
headerRight: () => <HeaderRightDivisionInfo id={id} active={dataDetail?.isActive} />,
headerRight: () => ((entityUser.role != "user" && entityUser.role != "coadmin") || isAdminDivision) && <HeaderRightDivisionInfo id={id} active={dataDetail?.isActive} />,
}}
/>
<ScrollView style={[Styles.h100]}>
@@ -161,6 +186,7 @@ export default function InformationDivision() {
<Text style={[Styles.textDefault, Styles.mv05]}>{dataMember.length} Anggota</Text>
<View style={[Styles.wrapPaper]}>
{
((entityUser.role != "user" && entityUser.role != "coadmin") || isAdminDivision) &&
dataDetail?.isActive && (
<BorderBottomItem
onPress={() => { router.push(`/division/${id}/add-member`) }}
@@ -188,7 +214,7 @@ export default function InformationDivision() {
<BorderBottomItem
key={index}
borderType="bottom"
onPress={() => { dataDetail?.isActive && handleChooseMember(item) }}
onPress={() => { dataDetail?.isActive && (isAdminDivision || (entityUser.role != "user" && entityUser.role != "coadmin")) && handleChooseMember(item) }}
icon={
<ImageUser src={`${ConstEnv.url_storage}/files/${item.img}`} size="sm" />
}

View File

@@ -189,7 +189,10 @@ export default function Index() {
return (
<BorderBottomItem
key={index}
onPress={() => { handleChooseData(item.id, item.name, item.isActive, item.idGroup) }}
onPress={() => {
entityUser.role != "user" &&
handleChooseData(item.id, item.name, item.isActive, item.idGroup)
}}
borderType="all"
icon={
<View style={[Styles.iconContent, ColorsStatus.lightGreen]}>

View File

@@ -16,7 +16,10 @@ export default function HeaderDiscussionGeneral() {
return (
<>
<ButtonMenuHeader onPress={() => { setVisible(true) }} />
{
entityUser.role != "user" && entityUser.role != "coadmin" &&
<ButtonMenuHeader onPress={() => { setVisible(true) }} />
}
<DrawerBottom animation="slide" isVisible={isVisible} setVisible={setVisible} title="Menu">
<View style={Styles.rowItemsCenter}>
<MenuItemRow

View File

@@ -62,7 +62,7 @@ export default function ProjectHome({ refreshing }: { refreshing: boolean }) {
width={width * 0.8}
height={235}
data={data}
loop={true}
loop={false}
autoPlay={false}
autoPlayReverse={false}
pagingEnabled={true}

View File

@@ -16,7 +16,10 @@ export default function HeaderMemberList() {
return (
<>
<ButtonMenuHeader onPress={() => { setVisible(true) }} />
{
entityUser.role != "user" &&
<ButtonMenuHeader onPress={() => { setVisible(true) }} />
}
<DrawerBottom animation="slide" isVisible={isVisible} setVisible={() => setVisible(false)} title="Menu">
<View style={Styles.rowItemsCenter}>
<MenuItemRow

View File

@@ -1,10 +1,14 @@
import { getApp, getApps, initializeApp } from '@react-native-firebase/app';
import { getMessaging, registerDeviceForRemoteMessages, setAutoInitEnabled } from '@react-native-firebase/messaging';
import {
getMessaging,
getToken as getMessagingToken,
setAutoInitEnabled,
} from '@react-native-firebase/messaging';
import * as Notifications from 'expo-notifications';
import { useEffect } from 'react';
import { PermissionsAndroid, Platform } from 'react-native';
// Your Firebase project configuration
// Firebase config
const RNfirebaseConfig = {
apiKey: "AIzaSyB2hbsW91J3oRQx4_jgrCCNY0tNt5-21e8",
authDomain: "googleapis.com",
@@ -15,14 +19,15 @@ const RNfirebaseConfig = {
databaseURL: "https://mobile-darmasaba-default-rtdb.asia-southeast1.firebasedatabase.app/"
};
const initializeFirebase = async () => {
try {
const app = getApps().length ? getApp() : initializeApp(RNfirebaseConfig);
const mess = getMessaging(app);
await registerDeviceForRemoteMessages(mess);
setAutoInitEnabled(mess, true);
return mess
// await registerDeviceForRemoteMessages(mess);
// `registerDeviceForRemoteMessages` tidak perlu lagi
await setAutoInitEnabled(mess, true);
return mess;
} catch (error) {
console.error('Failed to initialize Firebase:', error);
}
@@ -31,17 +36,16 @@ const initializeFirebase = async () => {
export const requestPermission = async () => {
try {
if (Platform.OS === 'android') {
const cek = await PermissionsAndroid.check(PermissionsAndroid.PERMISSIONS.POST_NOTIFICATIONS)
const cek = await PermissionsAndroid.check(
PermissionsAndroid.PERMISSIONS.POST_NOTIFICATIONS
);
if (!cek) {
const granted = await PermissionsAndroid.request(
PermissionsAndroid.PERMISSIONS.POST_NOTIFICATIONS
);
if (granted === PermissionsAndroid.RESULTS.GRANTED) {
return true
}
return false
return granted === PermissionsAndroid.RESULTS.GRANTED;
}
return true
return true;
} else if (Platform.OS === 'ios') {
const { status } = await Notifications.requestPermissionsAsync();
return status === 'granted';
@@ -54,10 +58,13 @@ export const requestPermission = async () => {
export const getToken = async () => {
try {
const mess = await initializeFirebase();
const token = await mess?.getToken();
if (!mess) return null;
// pakai modular API
const token = await getMessagingToken(mess);
return token;
} catch (error) {
console.error("Error getting token:", error);
console.error('Error getting token:', error);
}
};
@@ -73,4 +80,4 @@ export const useNotification = () => {
initializeAndSetup();
}, []);
};
};