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

View File

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

View File

@@ -135,7 +135,8 @@ export default function MemberDiscussionDetail() {
router.push(`/member/${chooseUser.idUser}`) router.push(`/member/${chooseUser.idUser}`)
}} }}
/> />
{
entityUser.role != "user" && entityUser.role != "coadmin" &&
<MenuItemRow <MenuItemRow
icon={<MaterialCommunityIcons name="account-remove" color="black" size={25} />} icon={<MaterialCommunityIcons name="account-remove" color="black" size={25} />}
title="Keluarkan" title="Keluarkan"
@@ -151,6 +152,8 @@ export default function MemberDiscussionDetail() {
}} }}
/> />
}
</View> </View>
</DrawerBottom> </DrawerBottom>
</SafeAreaView> </SafeAreaView>

View File

@@ -7,7 +7,7 @@ import SkeletonContent from "@/components/skeletonContent";
import Text from "@/components/Text"; import Text from "@/components/Text";
import { ConstEnv } from "@/constants/ConstEnv"; import { ConstEnv } from "@/constants/ConstEnv";
import Styles from "@/constants/Styles"; import Styles from "@/constants/Styles";
import { apiGetDiscussion } from "@/lib/api"; import { apiGetDiscussion, apiGetDivisionOneFeature } from "@/lib/api";
import { useAuthSession } from "@/providers/AuthProvider"; import { useAuthSession } from "@/providers/AuthProvider";
import { AntDesign, Feather, Ionicons } from "@expo/vector-icons"; import { AntDesign, Feather, Ionicons } from "@expo/vector-icons";
import { router, useLocalSearchParams } from "expo-router"; import { router, useLocalSearchParams } from "expo-router";
@@ -41,6 +41,30 @@ export default function DiscussionDivision() {
const [waiting, setWaiting] = useState(false) const [waiting, setWaiting] = useState(false)
const [status, setStatus] = useState<'true' | 'false'>('true') const [status, setStatus] = useState<'true' | 'false'>('true')
const [refreshing, setRefreshing] = useState(false) 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) { async function handleLoad(loading: boolean, thisPage: number) {
try { try {
@@ -80,6 +104,10 @@ export default function DiscussionDivision() {
}, 1000); }, 1000);
} }
useEffect(() => {
handleCheckMember()
}, [])
const handleRefresh = async () => { const handleRefresh = async () => {
setRefreshing(true) setRefreshing(true)
handleLoad(false, 1) handleLoad(false, 1)
@@ -101,6 +129,8 @@ export default function DiscussionDivision() {
return ( return (
<View style={[Styles.p15, { flex: 1 }]}> <View style={[Styles.p15, { flex: 1 }]}>
{
((entityUser.role != "user" && entityUser.role != "coadmin") || isAdminDivision) &&
<View> <View>
<View style={[Styles.wrapBtnTab]}> <View style={[Styles.wrapBtnTab]}>
<ButtonTab <ButtonTab
@@ -120,6 +150,8 @@ export default function DiscussionDivision() {
</View> </View>
<InputSearch onChange={setSearch} /> <InputSearch onChange={setSearch} />
</View> </View>
}
<View style={[{ flex: 2 }, Styles.mt05]}> <View style={[{ flex: 2 }, Styles.mt05]}>
{ {

View File

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

View File

@@ -189,7 +189,10 @@ export default function Index() {
return ( return (
<BorderBottomItem <BorderBottomItem
key={index} 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" borderType="all"
icon={ icon={
<View style={[Styles.iconContent, ColorsStatus.lightGreen]}> <View style={[Styles.iconContent, ColorsStatus.lightGreen]}>

View File

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

View File

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

View File

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

View File

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