Compare commits

...

32 Commits

Author SHA1 Message Date
bd82b7c427 Merge pull request 'amalia/26-sept-25' (#43) from amalia/26-sept-25 into join
Reviewed-on: bip/mobile-darmasaba#43
2025-09-26 17:42:28 +08:00
a6c96105d2 upd: text input komentar
Deskripsi:
- android input komentar pada android

No Issues
2025-09-26 16:52:35 +08:00
14e9bf15c7 upd: dokumen divisi
Deskripsi:
- update akses role pada dokumen divisi

No Issues
2025-09-26 15:08:59 +08:00
907b56feaf upd: text input
Deskripsi:
- text input pada android

No Issues
2025-09-26 12:22:12 +08:00
2341a46992 upd: task divisi
Deskripsi:
- user role akses

No Issues
2025-09-26 11:03:55 +08:00
43a91c6481 Merge pull request 'amalia/25-sept-25' (#42) from amalia/25-sept-25 into join
Reviewed-on: bip/mobile-darmasaba#42
2025-09-26 10:22:11 +08:00
ecc41c905f upd 2025-09-25 17:13:44 +08:00
65d53951c3 upd: caraousel kegiatan home
Deskripsi:
- loop false pada home caraousel kegiatan

No Issues
2025-09-25 11:27:51 +08:00
c2597b25bf upd: push notification
Deskripsi:
- update push notification warning

No Issues
2025-09-25 11:22:39 +08:00
f1b3eecbbe Merge pull request 'amalia/24-sept-25' (#41) from amalia/24-sept-25 into join
Reviewed-on: bip/mobile-darmasaba#41
2025-09-24 17:39:23 +08:00
f042e32d98 upd: push notification ios
Deskripsi:
- push notification foreground dan background pada ios

No Issues
2025-09-24 17:37:00 +08:00
93c492ac71 fix: edit profile
Deskripsi:
- update keyboard avoiding pada edit profile

No Issues
2025-09-24 16:19:33 +08:00
6cca0a3d08 fix : multiline text input
Deskripsi:
- multiline pada text input komentar diskusi umum dan diskusi divisi

No Issues
2025-09-24 16:11:57 +08:00
bbb25a30d2 fix : create divisi
Deskripsi:
- router delete pada saat setelah tambah divisi

No Issues
2025-09-24 14:42:54 +08:00
46e269b45f fix: drawer bottom
Deskripsi:
- scrooll data pada drawer bottom

No Issues
2025-09-24 14:08:04 +08:00
4725d27f74 fix: list project
Deskripsi:
- filter pada setiap user role
- fitur filter disetiap user role

No Issues
2025-09-24 11:01:18 +08:00
ae74791a1c Merge pull request 'amalia/23-sept-25' (#40) from amalia/23-sept-25 into join
Reviewed-on: bip/mobile-darmasaba#40
2025-09-23 17:47:13 +08:00
ba453ad027 upd: ios update 2025-09-23 13:55:50 +08:00
187e9dd19e upd: version code 2025-09-23 12:15:58 +08:00
040cab4f5e Merge pull request 'amalia/22-sept-25' (#39) from amalia/22-sept-25 into join
Reviewed-on: bip/mobile-darmasaba#39
2025-09-22 17:44:20 +08:00
7442d01551 upd: text error login 2025-09-22 17:43:07 +08:00
180fbeede9 fix: project
Deskripsi:
- fix fungsi tambah project saat user role selain developer dan supadmin

No Issues
2025-09-22 17:33:24 +08:00
d0d40cb1a7 fix : tampilan
Deskripsi:
- update tampilan diskusi item > judul melebihi container

No Issues
2025-09-22 17:22:32 +08:00
8a25c2f672 Merge pull request 'amalia/12-sept-25' (#38) from amalia/12-sept-25 into join
Reviewed-on: bip/mobile-darmasaba#38
2025-09-12 18:23:44 +08:00
d31c3677c9 upd: icon gambar 2025-09-12 16:35:56 +08:00
19b02ffc01 upd: easignore 2025-09-11 14:43:10 +08:00
b9b615636b fix: calendar
Deskripsi:
- tinggi scroll pada tambah data
- tinggi scroll pada edit tambah anggota
- checked anggota pada edit tambah anggota
- on press disable saat user telah menjadi anggota pada edit tambah anggota

No Issues
2025-09-11 11:44:34 +08:00
d3e7ef9623 Merge pull request 'amalia/09-sept-25' (#37) from amalia/09-sept-25 into join
Reviewed-on: bip/mobile-darmasaba#37
2025-09-09 17:26:53 +08:00
89bf659598 Merge pull request 'upd: kode verification' (#36) from amalia/04-sept-25 into join
Reviewed-on: bip/mobile-darmasaba#36
2025-09-04 18:39:14 +08:00
c435eb1503 Merge pull request 'amalia/04-sept-25' (#35) from amalia/04-sept-25 into join
Reviewed-on: bip/mobile-darmasaba#35
2025-09-04 18:19:35 +08:00
75c95b5c92 Merge pull request 'amalia/03-sept-25' (#34) from amalia/03-sept-25 into join
Reviewed-on: bip/mobile-darmasaba#34
2025-09-03 17:37:36 +08:00
bc590b8cb5 Merge pull request 'amalia/01-sept-25' (#33) from amalia/01-sept-25 into join
Reviewed-on: bip/mobile-darmasaba#33
2025-09-01 17:30:53 +08:00
39 changed files with 537 additions and 351 deletions

View File

@@ -39,5 +39,7 @@ app-example
x.ts x.ts
x.sh x.sh
/android
/ios .env
android/

View File

@@ -0,0 +1,4 @@
kotlin version: 2.0.21
error message: The daemon has terminated unexpectedly on startup attempt #1 with error code: 0. The daemon process output:
1. Kotlin compile daemon is ready

View File

@@ -4,7 +4,7 @@ export default {
expo: { expo: {
name: "Desa+", name: "Desa+",
slug: "mobile-darmasaba", slug: "mobile-darmasaba",
version: "1.0.3", version: "1.0.5", // Versi aplikasi (App Store)
jsEngine: "jsc", jsEngine: "jsc",
orientation: "portrait", orientation: "portrait",
icon: "./assets/images/logo-icon-small.png", icon: "./assets/images/logo-icon-small.png",
@@ -14,15 +14,16 @@ export default {
ios: { ios: {
supportsTablet: true, supportsTablet: true,
bundleIdentifier: "mobiledarmasaba.app", bundleIdentifier: "mobiledarmasaba.app",
buildNumber: "2",
infoPlist: { infoPlist: {
ITSAppUsesNonExemptEncryption: false, ITSAppUsesNonExemptEncryption: false,
CFBundleDisplayName: "Desa+" CFBundleDisplayName: "Desa+"
}, },
googleServicesFile: "./ios/Desa/GoogleService-Info.plist" googleServicesFile: process.env.IOS_GOOGLE_SERVICES_FILE
}, },
android: { android: {
package: "mobiledarmasaba.app", package: "mobiledarmasaba.app",
versionCode: 7, versionCode: 9,
adaptiveIcon: { adaptiveIcon: {
foregroundImage: "./assets/images/logo-icon-small.png", foregroundImage: "./assets/images/logo-icon-small.png",
backgroundColor: "#ffffff" backgroundColor: "#ffffff"
@@ -59,7 +60,7 @@ export default {
"@react-native-firebase/app", "@react-native-firebase/app",
{ {
ios: { ios: {
googleServicesFile: "./ios/Desa/GoogleService-Info.plist" googleServicesFile: process.env.IOS_GOOGLE_SERVICES_FILE
} }
} }
] ]

View File

@@ -153,14 +153,13 @@ 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}
desc={data?.desc} desc={data?.desc}
leftBottomInfo={ leftBottomInfo={
<View style={[Styles.rowItemsCenter]}> <View style={[Styles.rowItemsCenter]}>
@@ -168,6 +167,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]}>
@@ -214,11 +218,16 @@ export default function DetailDiscussionGeneral() {
bg="white" bg="white"
onChange={setKomentar} onChange={setKomentar}
value={komentar} value={komentar}
multiline
itemRight={ itemRight={
<Pressable onPress={() => { <Pressable onPress={() => {
(komentar != '' && data?.status === 1 && data?.isActive && (memberDiscussion || (entityUser.role != "user" && entityUser.role != "coadmin"))) (komentar != '' && data?.status === 1 && data?.isActive && (memberDiscussion || (entityUser.role != "user" && entityUser.role != "coadmin")))
&& handleKomentar() && handleKomentar()
}}> }}
style={[
Platform.OS == 'android' && Styles.mb15,
]}
>
<MaterialIcons name="send" size={25} style={(komentar == '' || data?.status === 2 || !data?.isActive || (!memberDiscussion && (entityUser.role == "user" || entityUser.role == "coadmin"))) ? Styles.cGray : Styles.cDefault} /> <MaterialIcons name="send" size={25} style={(komentar == '' || data?.status === 2 || !data?.isActive || (!memberDiscussion && (entityUser.role == "user" || entityUser.role == "coadmin"))) ? Styles.cGray : Styles.cDefault} />
</Pressable> </Pressable>
} }

View File

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

View File

@@ -135,22 +135,25 @@ export default function MemberDiscussionDetail() {
router.push(`/member/${chooseUser.idUser}`) 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> </View>
</DrawerBottom> </DrawerBottom>
</SafeAreaView> </SafeAreaView>

View File

@@ -142,6 +142,7 @@ export default function AddMemberCalendarEvent() {
} }
<ScrollView <ScrollView
showsVerticalScrollIndicator={false} showsVerticalScrollIndicator={false}
style={[Styles.h100]}
> >
{ {
data.length > 0 ? data.length > 0 ?
@@ -153,7 +154,6 @@ export default function AddMemberCalendarEvent() {
style={[Styles.itemSelectModal]} style={[Styles.itemSelectModal]}
onPress={() => { onPress={() => {
!found && onChoose(item.idUser, item.name, item.img) !found && onChoose(item.idUser, item.name, item.img)
onChoose(item.idUser, item.name, item.img)
}} }}
> >
<View style={[Styles.rowItemsCenter]}> <View style={[Styles.rowItemsCenter]}>
@@ -166,7 +166,7 @@ export default function AddMemberCalendarEvent() {
</View> </View>
</View> </View>
{ {
selectMember.some((i: any) => i.idUser == item.id) && <AntDesign name="check" size={20} color={'black'} /> selectMember.some((i: any) => i.idUser == item.idUser) && <AntDesign name="check" size={20} color={'black'} />
} }
</Pressable> </Pressable>
) )

View File

@@ -131,6 +131,7 @@ export default function CreateCalendarAddMember() {
} }
<ScrollView <ScrollView
showsVerticalScrollIndicator={false} showsVerticalScrollIndicator={false}
style={[Styles.h100]}
> >
{ {

View File

@@ -304,6 +304,7 @@ export default function DiscussionDetail() {
bg="white" bg="white"
type="default" type="default"
round round
multiline
placeholder="Kirim Komentar" placeholder="Kirim Komentar"
onChange={setKomentar} onChange={setKomentar}
value={komentar} value={komentar}
@@ -323,12 +324,15 @@ export default function DiscussionDetail() {
entityUser.role == "cosupadmin") && entityUser.role == "cosupadmin") &&
handleKomentar(); handleKomentar();
}} }}
style={[
Platform.OS == 'android' && Styles.mb15,
]}
> >
<MaterialIcons <MaterialIcons
name="send" name="send"
size={25} size={25}
style={ style={
komentar == "" || [komentar == "" ||
loadingSend || loadingSend ||
data?.status == 2 || data?.status == 2 ||
data?.isActive == false || data?.isActive == false ||
@@ -336,7 +340,8 @@ export default function DiscussionDetail() {
entityUser.role == "coadmin") && entityUser.role == "coadmin") &&
!isMemberDivision) !isMemberDivision)
? Styles.cGray ? Styles.cGray
: Styles.cDefault : Styles.cDefault,
]
} }
/> />
</Pressable> </Pressable>

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

View File

@@ -18,6 +18,7 @@ import Styles from "@/constants/Styles";
import { import {
apiDocumentDelete, apiDocumentDelete,
apiDocumentRename, apiDocumentRename,
apiGetDivisionOneFeature,
apiGetDocument, apiGetDocument,
apiShareDocument, apiShareDocument,
} from "@/lib/api"; } from "@/lib/api";
@@ -85,6 +86,8 @@ export default function DocumentDivision() {
const update = useSelector((state: any) => state.dokumenUpdate) const update = useSelector((state: any) => state.dokumenUpdate)
const [refreshing, setRefreshing] = useState(false) const [refreshing, setRefreshing] = useState(false)
const [loadingOpen, setLoadingOpen] = useState(false) const [loadingOpen, setLoadingOpen] = useState(false)
const [isMemberDivision, setIsMemberDivision] = useState(false)
const entityUser = useSelector((state: any) => state.user)
const [bodyRename, setBodyRename] = useState({ const [bodyRename, setBodyRename] = useState({
id: "", id: "",
name: "", name: "",
@@ -93,6 +96,24 @@ export default function DocumentDivision() {
extension: "", extension: "",
}); });
async function handleCheckMember() {
try {
const hasil = await decryptToken(String(token?.current));
const response = await apiGetDivisionOneFeature({
id,
user: hasil,
cat: "check-member",
});
setIsMemberDivision(response.data);
} catch (error) {
console.error(error);
}
}
useEffect(() => {
handleCheckMember()
}, [id])
async function handleLoad(loading: boolean) { async function handleLoad(loading: boolean) {
try { try {
setLoading(loading) setLoading(loading)
@@ -347,7 +368,7 @@ export default function DocumentDivision() {
}} }}
/> />
) : ( ) : (
<HeaderRightDocument path={path} /> <HeaderRightDocument path={path} isMember={isMemberDivision} />
), ),
}} }}
/> />
@@ -407,6 +428,7 @@ export default function DocumentDivision() {
: `${item.name}.${item.extension}` : `${item.name}.${item.extension}`
} }
dateTime={item.createdAt} dateTime={item.createdAt}
canChecked={(entityUser.role != "user" && entityUser.role != "coadmin") || isMemberDivision}
onChecked={() => { onChecked={() => {
handleCheckboxChange(index); handleCheckboxChange(index);
}} }}

View File

@@ -125,7 +125,7 @@ export default function DetailTaskDivision() {
<SectionTanggalTugasTask refreshing={refreshing} isMemberDivision={isMemberDivision} /> <SectionTanggalTugasTask refreshing={refreshing} isMemberDivision={isMemberDivision} />
<SectionFileTask refreshing={refreshing} isMemberDivision={isMemberDivision} /> <SectionFileTask refreshing={refreshing} isMemberDivision={isMemberDivision} />
<SectionLinkTask refreshing={refreshing} isMemberDivision={isMemberDivision} /> <SectionLinkTask refreshing={refreshing} isMemberDivision={isMemberDivision} />
<SectionMemberTask refreshing={refreshing} isMemberDivision={isMemberDivision} /> <SectionMemberTask refreshing={refreshing} isAdminDivision={isAdminDivision} />
</View> </View>
</ScrollView> </ScrollView>
</SafeAreaView> </SafeAreaView>

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

@@ -9,6 +9,7 @@ import { setFormCreateDivision } from "@/lib/divisionCreate";
import { setUpdateDivision } from "@/lib/divisionUpdate"; import { setUpdateDivision } from "@/lib/divisionUpdate";
import { useAuthSession } from "@/providers/AuthProvider"; import { useAuthSession } from "@/providers/AuthProvider";
import { AntDesign } from "@expo/vector-icons"; import { AntDesign } from "@expo/vector-icons";
import { StackActions, useNavigation } from "@react-navigation/native";
import { router, Stack, useLocalSearchParams } from "expo-router"; import { router, Stack, useLocalSearchParams } from "expo-router";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { Pressable, SafeAreaView, ScrollView, View } from "react-native"; import { Pressable, SafeAreaView, ScrollView, View } from "react-native";
@@ -23,6 +24,7 @@ type Props = {
export default function CreateDivisionAddAdmin() { export default function CreateDivisionAddAdmin() {
const { token, decryptToken } = useAuthSession() const { token, decryptToken } = useAuthSession()
const navigation = useNavigation()
const { id } = useLocalSearchParams<{ id: string }>() const { id } = useLocalSearchParams<{ id: string }>()
const [dataOld, setDataOld] = useState<Props[]>([]) const [dataOld, setDataOld] = useState<Props[]>([])
const [data, setData] = useState<Props[]>([]) const [data, setData] = useState<Props[]>([])
@@ -57,7 +59,8 @@ export default function CreateDivisionAddAdmin() {
Toast.show({ type: 'small', text1: 'Berhasil membuat divisi', }) Toast.show({ type: 'small', text1: 'Berhasil membuat divisi', })
dispatch(setFormCreateDivision({ admin: [], member: [], data: { idGroup: '', name: '', desc: '' } })) dispatch(setFormCreateDivision({ admin: [], member: [], data: { idGroup: '', name: '', desc: '' } }))
dispatch(setUpdateDivision(!updateDivision)) dispatch(setUpdateDivision(!updateDivision))
router.replace(`/division/`) navigation.dispatch(StackActions.pop(3))
// router.replace(`/division/`)
} else { } else {
Toast.show({ type: 'small', text1: response.message, }) Toast.show({ type: 'small', text1: response.message, })
} }

View File

@@ -10,11 +10,14 @@ import { apiEditProfile, apiGetProfile } from "@/lib/api";
import { setEntities } from "@/lib/entitiesSlice"; import { setEntities } from "@/lib/entitiesSlice";
import { useAuthSession } from "@/providers/AuthProvider"; import { useAuthSession } from "@/providers/AuthProvider";
import { MaterialCommunityIcons } from "@expo/vector-icons"; import { MaterialCommunityIcons } from "@expo/vector-icons";
import { useHeaderHeight } from "@react-navigation/elements";
import * as ImagePicker from "expo-image-picker"; import * as ImagePicker from "expo-image-picker";
import { router, Stack } from "expo-router"; import { router, Stack } from "expo-router";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { import {
Image, Image,
KeyboardAvoidingView,
Platform,
Pressable, Pressable,
SafeAreaView, SafeAreaView,
ScrollView, ScrollView,
@@ -37,6 +40,7 @@ type Props = {
}; };
export default function EditProfile() { export default function EditProfile() {
const headerHeight = useHeaderHeight()
const dispatch = useDispatch() const dispatch = useDispatch()
const entities = useSelector((state: any) => state.entities) const entities = useSelector((state: any) => state.entities)
const { token, decryptToken } = useAuthSession() const { token, decryptToken } = useAuthSession()
@@ -231,116 +235,122 @@ export default function EditProfile() {
), ),
}} }}
/> />
<ScrollView style={[Styles.h100]}> <KeyboardAvoidingView
<View style={[Styles.p15]}> style={[Styles.h100]}
<View style={{ justifyContent: "center", alignItems: "center" }}> behavior={Platform.OS === 'ios' ? 'padding' : undefined}
{ keyboardVerticalOffset={headerHeight}
selectedImage != undefined ? ( >
<Pressable onPress={pickImageAsync}> <ScrollView showsVerticalScrollIndicator={false}>
<Image <View style={[Styles.p15]}>
src={ <View style={{ justifyContent: "center", alignItems: "center" }}>
typeof selectedImage === "string" {
? selectedImage selectedImage != undefined ? (
: selectedImage.uri <Pressable onPress={pickImageAsync}>
} <Image
style={[Styles.userProfileBig]} src={
onError={() => { setErrorImg(true) }} typeof selectedImage === "string"
/> ? selectedImage
<View style={[Styles.absoluteIconPicker]}> : selectedImage.uri
<MaterialCommunityIcons name="image" color={'white'} size={15} /> }
</View> style={[Styles.userProfileBig]}
</Pressable> onError={() => { setErrorImg(true) }}
) : ( />
<Pressable onPress={pickImageAsync}> <View style={[Styles.absoluteIconPicker]}>
<Image <MaterialCommunityIcons name="image" color={'white'} size={15} />
source={errorImg ? require("../../assets/images/user.jpg") : { uri: `${ConstEnv.url_storage}/files/${data?.img}` }} </View>
style={[Styles.userProfileBig]} </Pressable>
onError={() => { setErrorImg(true) }} ) : (
/> <Pressable onPress={pickImageAsync}>
<View style={[Styles.absoluteIconPicker]}> <Image
<MaterialCommunityIcons name="image" color={'white'} size={15} /> source={errorImg ? require("../../assets/images/user.jpg") : { uri: `${ConstEnv.url_storage}/files/${data?.img}` }}
</View> style={[Styles.userProfileBig]}
</Pressable> onError={() => { setErrorImg(true) }}
) />
} <View style={[Styles.absoluteIconPicker]}>
<MaterialCommunityIcons name="image" color={'white'} size={15} />
</View>
</Pressable>
)
}
</View>
<SelectForm
label="Jabatan"
placeholder="Pilih Jabatan"
value={choosePosition.label}
required
onPress={() => {
setValChoose(choosePosition.val);
setValSelect("position");
setSelect(true);
}}
error={error.position}
errorText="Jabatan tidak boleh kosong"
/>
<InputForm
label="NIK"
type="numeric"
placeholder="NIK"
required
value={data?.nik}
error={error.nik}
errorText="NIK Harus 16 Karakter"
onChange={val => {
validationForm("nik", val)
}}
/>
<InputForm
label="Nama"
type="default"
placeholder="Nama"
required
value={data?.name}
error={error.name}
errorText="Nama tidak boleh kosong"
onChange={val => {
validationForm("name", val)
}}
/>
<InputForm
label="Email"
type="default"
placeholder="Email"
required
value={data?.email}
error={error.email}
errorText="Email tidak valid"
onChange={val => {
validationForm("email", val)
}}
/>
<InputForm
label="Nomor Telepon"
type="numeric"
placeholder="8XX-XXX-XXX"
required
itemLeft={<Text>+62</Text>}
value={data?.phone}
error={error.phone}
errorText="Nomor Telepon tidak valid"
onChange={val => {
validationForm("phone", val)
}}
/>
<SelectForm
label="Jenis Kelamin"
placeholder="Pilih Jenis Kelamin"
value={chooseGender.label}
required
onPress={() => {
setValChoose(chooseGender.val);
setValSelect("gender");
setSelect(true);
}}
error={error.gender}
errorText="Jenis Kelamin tidak boleh kosong"
/>
</View> </View>
<SelectForm </ScrollView>
label="Jabatan" </KeyboardAvoidingView>
placeholder="Pilih Jabatan"
value={choosePosition.label}
required
onPress={() => {
setValChoose(choosePosition.val);
setValSelect("position");
setSelect(true);
}}
error={error.position}
errorText="Jabatan tidak boleh kosong"
/>
<InputForm
label="NIK"
type="numeric"
placeholder="NIK"
required
value={data?.nik}
error={error.nik}
errorText="NIK Harus 16 Karakter"
onChange={val => {
validationForm("nik", val)
}}
/>
<InputForm
label="Nama"
type="default"
placeholder="Nama"
required
value={data?.name}
error={error.name}
errorText="Nama tidak boleh kosong"
onChange={val => {
validationForm("name", val)
}}
/>
<InputForm
label="Email"
type="default"
placeholder="Email"
required
value={data?.email}
error={error.email}
errorText="Email tidak valid"
onChange={val => {
validationForm("email", val)
}}
/>
<InputForm
label="Nomor Telepon"
type="numeric"
placeholder="8XX-XXX-XXX"
required
itemLeft={<Text>+62</Text>}
value={data?.phone}
error={error.phone}
errorText="Nomor Telepon tidak valid"
onChange={val => {
validationForm("phone", val)
}}
/>
<SelectForm
label="Jenis Kelamin"
placeholder="Pilih Jenis Kelamin"
value={chooseGender.label}
required
onPress={() => {
setValChoose(chooseGender.val);
setValSelect("gender");
setSelect(true);
}}
error={error.gender}
errorText="Jenis Kelamin tidak boleh kosong"
/>
</View>
</ScrollView>
<ModalSelect <ModalSelect
category={valSelect} category={valSelect}

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

@@ -42,6 +42,7 @@ export default function CreateProject() {
const taskCreate = useSelector((state: any) => state.taskCreate); const taskCreate = useSelector((state: any) => state.taskCreate);
const update = useSelector((state: any) => state.projectUpdate) const update = useSelector((state: any) => state.projectUpdate)
const entityUser = useSelector((state: any) => state.user); const entityUser = useSelector((state: any) => state.user);
const userLogin = useSelector((state: any) => state.entities)
const [fileForm, setFileForm] = useState<any[]>([]) const [fileForm, setFileForm] = useState<any[]>([])
const [indexDelFile, setIndexDelFile] = useState<number>(0) const [indexDelFile, setIndexDelFile] = useState<number>(0)
const [disableBtn, setDisableBtn] = useState(true) const [disableBtn, setDisableBtn] = useState(true)
@@ -86,7 +87,7 @@ export default function CreateProject() {
} else { } else {
setError(error => ({ ...error, title: false })) setError(error => ({ ...error, title: false }))
} }
} else if (cat == "task" && hitung > 1) { } else if (cat == "task" && hitung > 2) {
if (taskCreate.length == 0) { if (taskCreate.length == 0) {
setError(error => ({ ...error, task: true })) setError(error => ({ ...error, task: true }))
} else { } else {
@@ -114,6 +115,13 @@ export default function CreateProject() {
validationForm('task', ''); validationForm('task', '');
}, [taskCreate]); }, [taskCreate]);
useEffect(() => {
if (entityUser.role != "supadmin" && entityUser.role != "developer") {
validationForm('group', userLogin.idGroup, userLogin.group);
}
}, []);
async function handleCreate() { async function handleCreate() {
try { try {
setLoading(true) setLoading(true)

View File

@@ -193,16 +193,19 @@ export default function ListProject() {
</Pressable> </Pressable>
</View> </View>
<View style={[Styles.mv05]}> <View style={[Styles.mv05]}>
<Text>Filter : {
{ entityUser.role != 'cosupadmin' && entityUser.role != 'admin' &&
(entityUser.role == "supadmin" || entityUser.role == "developer") && nameGroup <Text>Filter :
} {
{ (entityUser.role == "supadmin" || entityUser.role == "developer") && nameGroup
(entityUser.role == 'user' || entityUser.role == 'coadmin' || entityUser.role == 'cosupadmin') }
? (cat == 'null' || cat == 'undefined' || cat == undefined || cat == '' || cat == 'data-saya') ? 'Kegiatan Saya' : 'Semua Kegiatan' {
: '' (entityUser.role == 'user' || entityUser.role == 'coadmin')
} ? (cat == 'null' || cat == 'undefined' || cat == undefined || cat == '' || cat == 'data-saya') ? 'Kegiatan Saya' : 'Semua Kegiatan'
</Text> : ''
}
</Text>
}
</View> </View>
</View> </View>
<View style={[{ flex: 2 }]}> <View style={[{ flex: 2 }]}>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 27 KiB

View File

@@ -42,7 +42,7 @@ export default function ViewLogin({ onValidate }: Props) {
return Toast.show({ type: 'small', text1: response.message, position: 'top' }) return Toast.show({ type: 'small', text1: response.message, position: 'top' })
} }
} catch (error) { } catch (error) {
return Toast.show({ type: 'small', text1: 'Terjadi kesalahan', position: 'top' }) return Toast.show({ type: 'small', text1: `Terjadi kesalahan, coba lagi`, position: 'top' })
} finally { } finally {
setLoadingLogin(false) setLoadingLogin(false)
} }

View File

@@ -15,7 +15,9 @@ export default function DiscussionItem({ title, user, date, onPress }: Props) {
<Pressable style={[Styles.wrapItemDiscussion]} onPress={onPress}> <Pressable style={[Styles.wrapItemDiscussion]} onPress={onPress}>
<View style={[Styles.rowItemsCenter, Styles.mb10]}> <View style={[Styles.rowItemsCenter, Styles.mb10]}>
<Ionicons name="chatbox-ellipses-outline" size={22} color="black" style={Styles.mr10} /> <Ionicons name="chatbox-ellipses-outline" size={22} color="black" style={Styles.mr10} />
<Text style={{ fontWeight: 'bold' }} numberOfLines={1} ellipsizeMode="tail">{title}</Text> <View style={[{flex:1}]}>
<Text style={{ fontWeight: 'bold' }} numberOfLines={1} ellipsizeMode="tail">{title}</Text>
</View>
</View> </View>
<View style={Styles.rowSpaceBetween}> <View style={Styles.rowSpaceBetween}>
<View style={Styles.rowItemsCenter}> <View style={Styles.rowItemsCenter}>

View File

@@ -16,7 +16,10 @@ export default function HeaderDiscussionGeneral() {
return ( return (
<> <>
<ButtonMenuHeader onPress={() => { setVisible(true) }} /> {
entityUser.role != "user" && entityUser.role != "coadmin" &&
<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

@@ -15,7 +15,7 @@ import { InputForm } from "../inputForm";
import MenuItemRow from "../menuItemRow"; import MenuItemRow from "../menuItemRow";
import ModalFloat from "../modalFloat"; import ModalFloat from "../modalFloat";
export default function HeaderRightDocument({ path }: { path: string }) { export default function HeaderRightDocument({ path, isMember }: { path: string, isMember: boolean }) {
const [isVisible, setVisible] = useState(false); const [isVisible, setVisible] = useState(false);
const [newFolder, setNewFolder] = useState(false); const [newFolder, setNewFolder] = useState(false);
const { id } = useLocalSearchParams<{ id: string }>(); const { id } = useLocalSearchParams<{ id: string }>();
@@ -25,6 +25,7 @@ export default function HeaderRightDocument({ path }: { path: string }) {
const update = useSelector((state: any) => state.dokumenUpdate) const update = useSelector((state: any) => state.dokumenUpdate)
const [loading, setLoading] = useState(false) const [loading, setLoading] = useState(false)
const [loadingFolder, setLoadingFolder] = useState(false) const [loadingFolder, setLoadingFolder] = useState(false)
const entityUser = useSelector((state: any) => state.user)
async function handleCreateFolder() { async function handleCreateFolder() {
try { try {
@@ -102,11 +103,14 @@ export default function HeaderRightDocument({ path }: { path: string }) {
return ( return (
<> <>
<ButtonMenuHeader {
onPress={() => { ((entityUser.role != "user" && entityUser.role != "coadmin") || isMember) &&
setVisible(true); <ButtonMenuHeader
}} onPress={() => {
/> setVisible(true);
}}
/>
}
<DrawerBottom <DrawerBottom
animation="slide" animation="slide"
isVisible={isVisible} isVisible={isVisible}

View File

@@ -11,9 +11,10 @@ type Props = {
checked?: boolean checked?: boolean
onChecked?: () => void onChecked?: () => void
onPress?: () => void onPress?: () => void
canChecked?: boolean
} }
export default function ItemFile({ category, checked, dateTime, title, onChecked, onPress }: Props) { export default function ItemFile({ category, checked, dateTime, title, onChecked, onPress, canChecked }: Props) {
return ( return (
<View style={[Styles.wrapItemBorderBottom]}> <View style={[Styles.wrapItemBorderBottom]}>
<View style={[Styles.rowItemsCenter]}> <View style={[Styles.rowItemsCenter]}>
@@ -43,18 +44,22 @@ export default function ItemFile({ category, checked, dateTime, title, onChecked
</Pressable> </Pressable>
<View style={[Styles.rowSpaceBetween, { flex: 1, alignItems: 'center' }]}> <View style={[Styles.rowSpaceBetween, { flex: 1, alignItems: 'center' }]}>
<Pressable style={[Styles.ml10, {flex:1},]} onPress={onPress}> <Pressable style={[Styles.ml10, { flex: 1 },]} onPress={onPress}>
<Text style={[Styles.textDefault]} numberOfLines={1} ellipsizeMode="tail">{title}</Text> <Text style={[Styles.textDefault]} numberOfLines={1} ellipsizeMode="tail">{title}</Text>
<Text style={[Styles.textInformation]}>{dateTime}</Text> <Text style={[Styles.textInformation]}>{dateTime}</Text>
</Pressable> </Pressable>
<Pressable onPress={onChecked}> {
{ !canChecked ? <></>
checked :
? <MaterialCommunityIcons name="checkbox-marked-circle" size={25} color={'black'} /> <Pressable onPress={onChecked}>
: <MaterialCommunityIcons name="checkbox-blank-circle-outline" size={25} color={'#ced4da'} /> {
} checked
? <MaterialCommunityIcons name="checkbox-marked-circle" size={25} color={'black'} />
: <MaterialCommunityIcons name="checkbox-blank-circle-outline" size={25} color={'#ced4da'} />
}
</Pressable> </Pressable>
}
</View> </View>
</View> </View>
</View> </View>

View File

@@ -35,6 +35,7 @@ export default function DrawerBottom({ isVisible, setVisible, title, children, a
backdropTransitionInTiming={500} backdropTransitionInTiming={500}
backdropTransitionOutTiming={500} backdropTransitionOutTiming={500}
useNativeDriverForBackdrop={true} useNativeDriverForBackdrop={true}
propagateSwipe={true}
> >
{ {
keyboard ? keyboard ?
@@ -62,7 +63,7 @@ export default function DrawerBottom({ isVisible, setVisible, title, children, a
<MaterialIcons name="close" color="black" size={22} /> <MaterialIcons name="close" color="black" size={22} />
</Pressable> </Pressable>
</View> </View>
<View style={Styles.contentContainer}> <View style={[Styles.contentContainer, { flex: 1 }]}>
{children} {children}
</View> </View>
</View> </View>

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

@@ -40,10 +40,15 @@ export function InputForm({ label, value, placeholder, onChange, info, disable,
<View style={[ <View style={[
Styles.inputRoundForm, Styles.inputRoundForm,
itemRight != undefined ? Styles.inputRoundFormRight : Styles.inputRoundFormLeft, itemRight != undefined ? Styles.inputRoundFormRight : Styles.inputRoundFormLeft,
multiline && { alignItems: 'flex-end' },
round && Styles.round30, round && Styles.round30,
{ backgroundColor: bg && bg == 'white' ? 'white' : 'transparent' }, { backgroundColor: bg && bg == 'white' ? 'white' : 'transparent' },
error && { borderColor: "red" }, error && { borderColor: "red" },
Platform.OS == 'ios' ? { paddingVertical: 10 } : { paddingVertical: 0 }, Platform.OS == 'ios' ? { paddingVertical: 10 } : { paddingVertical: 0, minHeight: 40 },
{ alignItems: 'center' },
multiline
? { alignItems: "flex-end" } // multiline: tombol send di bawah
: { alignItems: "center" }, // default: tetap di tengah
]}> ]}>
{itemRight != undefined ? itemRight : itemLeft} {itemRight != undefined ? itemRight : itemLeft}
<TextInput <TextInput
@@ -53,7 +58,14 @@ export function InputForm({ label, value, placeholder, onChange, info, disable,
keyboardType={type} keyboardType={type}
onChangeText={onChange} onChangeText={onChange}
placeholderTextColor={'gray'} placeholderTextColor={'gray'}
style={[Styles.mh05, { width: width ? lebar * width / 100 : lebar * 0.78, color: 'black' }, Platform.OS == 'ios' ? { paddingVertical: 1 } : { paddingVertical: 5 }]} multiline={multiline}
numberOfLines={3}
style={[
Styles.mh05,
multiline && { height: '100%', maxHeight: 100 },
{ width: width ? lebar * width / 100 : lebar * 0.78, color: 'black' },
Platform.OS == 'ios' ? { paddingVertical: 1, paddingTop: 3 } : { paddingVertical: 0 },
]}
/> />
</View> </View>
{error && (<Text style={[Styles.textInformation, Styles.cError, Styles.mt05]}>{errorText}</Text>)} {error && (<Text style={[Styles.textInformation, Styles.cError, Styles.mt05]}>{errorText}</Text>)}

View File

@@ -16,7 +16,10 @@ export default function HeaderMemberList() {
return ( return (
<> <>
<ButtonMenuHeader onPress={() => { setVisible(true) }} /> {
entityUser.role != "user" &&
<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

@@ -30,16 +30,20 @@ export default function HeaderRightProjectList() {
}} }}
/> />
} }
<MenuItemRow {
icon={<AntDesign name="filter" color="black" size={25} />} (entityUser.role == "user" || entityUser.role == "coadmin" || entityUser.role == "supadmin" || entityUser.role == "developer") &&
title="Filter" <MenuItemRow
onPress={() => { icon={<AntDesign name="filter" color="black" size={25} />}
setVisible(false) title="Filter"
setTimeout(() => { onPress={() => {
setFilter(true) setVisible(false)
}, 600) setTimeout(() => {
}} setFilter(true)
/> }, 600)
}}
/>
}
</View> </View>
</DrawerBottom> </DrawerBottom>
<ModalFilter <ModalFilter

View File

@@ -26,7 +26,7 @@ type Props = {
position: string; position: string;
}; };
export default function SectionMemberTask({ refreshing, isMemberDivision }: { refreshing: boolean, isMemberDivision: boolean }) { export default function SectionMemberTask({ refreshing, isAdminDivision }: { refreshing: boolean, isAdminDivision: boolean }) {
const [isModal, setModal] = useState(false); const [isModal, setModal] = useState(false);
const entityUser = useSelector((state: any) => state.user); const entityUser = useSelector((state: any) => state.user);
const { token, decryptToken } = useAuthSession(); const { token, decryptToken } = useAuthSession();
@@ -168,7 +168,7 @@ export default function SectionMemberTask({ refreshing, isMemberDivision }: { re
{ {
(entityUser.role != "user" && entityUser.role != "coadmin") || isMemberDivision (entityUser.role != "user" && entityUser.role != "coadmin") || isAdminDivision
? ?
<MenuItemRow <MenuItemRow
icon={ icon={

File diff suppressed because one or more lines are too long

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.0 KiB

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 13 KiB

View File

@@ -1,95 +1,99 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"> <plist version="1.0">
<dict> <dict>
<key>CADisableMinimumFrameDurationOnPhone</key> <key>CADisableMinimumFrameDurationOnPhone</key>
<true/> <true/>
<key>CFBundleDevelopmentRegion</key> <key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string> <string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key> <key>CFBundleDisplayName</key>
<string>Desa+</string> <string>Desa+</string>
<key>CFBundleExecutable</key> <key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string> <string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key> <key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string> <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key> <key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string> <string>6.0</string>
<key>CFBundleName</key> <key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string> <string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key> <key>CFBundlePackageType</key>
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string> <string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
<string>1.0.2</string> <string>1.0.5</string>
<key>CFBundleSignature</key> <key>CFBundleSignature</key>
<string>????</string> <string>????</string>
<key>CFBundleURLTypes</key> <key>CFBundleURLTypes</key>
<array> <array>
<dict> <dict>
<key>CFBundleURLSchemes</key> <key>CFBundleURLSchemes</key>
<array> <array>
<string>myapp</string> <string>myapp</string>
<string>mobiledarmasaba.app</string> <string>mobiledarmasaba.app</string>
</array> </array>
</dict> </dict>
<dict> <dict>
<key>CFBundleURLSchemes</key> <key>CFBundleURLSchemes</key>
<array> <array>
<string>exp+mobile-darmasaba</string> <string>exp+mobile-darmasaba</string>
</array> </array>
</dict> </dict>
</array> </array>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>1</string> <string>2</string>
<key>ITSAppUsesNonExemptEncryption</key> <key>ITSAppUsesNonExemptEncryption</key>
<false/> <false/>
<key>LSMinimumSystemVersion</key> <key>LSMinimumSystemVersion</key>
<string>12.0</string> <string>12.0</string>
<key>LSRequiresIPhoneOS</key> <key>LSRequiresIPhoneOS</key>
<true/> <true/>
<key>NSAppTransportSecurity</key> <key>NSAppTransportSecurity</key>
<dict> <dict>
<key>NSAllowsArbitraryLoads</key> <key>NSAllowsArbitraryLoads</key>
<false/> <false/>
<key>NSAllowsLocalNetworking</key> <key>NSAllowsLocalNetworking</key>
<true/> <true/>
</dict> </dict>
<key>NSCameraUsageDescription</key> <key>NSCameraUsageDescription</key>
<string>Allow $(PRODUCT_NAME) to access your camera</string> <string>Allow $(PRODUCT_NAME) to access your camera</string>
<key>NSMicrophoneUsageDescription</key> <key>NSMicrophoneUsageDescription</key>
<string>Allow $(PRODUCT_NAME) to access your microphone</string> <string>Allow $(PRODUCT_NAME) to access your microphone</string>
<key>NSPhotoLibraryAddUsageDescription</key> <key>NSPhotoLibraryAddUsageDescription</key>
<string>Allow $(PRODUCT_NAME) to save photos</string> <string>Allow $(PRODUCT_NAME) to save photos</string>
<key>NSPhotoLibraryUsageDescription</key> <key>NSPhotoLibraryUsageDescription</key>
<string>Allow $(PRODUCT_NAME) to access your photos</string> <string>Allow $(PRODUCT_NAME) to access your photos</string>
<key>NSUserActivityTypes</key> <key>NSUserActivityTypes</key>
<array> <array>
<string>$(PRODUCT_BUNDLE_IDENTIFIER).expo.index_route</string> <string>$(PRODUCT_BUNDLE_IDENTIFIER).expo.index_route</string>
</array> </array>
<key>UILaunchStoryboardName</key> <key>UIBackgroundModes</key>
<string>SplashScreen</string> <array>
<key>UIRequiredDeviceCapabilities</key> <string>remote-notification</string>
<array> </array>
<string>arm64</string> <key>UILaunchStoryboardName</key>
</array> <string>SplashScreen</string>
<key>UIRequiresFullScreen</key> <key>UIRequiredDeviceCapabilities</key>
<false/> <array>
<key>UIStatusBarStyle</key> <string>arm64</string>
<string>UIStatusBarStyleDefault</string> </array>
<key>UISupportedInterfaceOrientations</key> <key>UIRequiresFullScreen</key>
<array> <false/>
<string>UIInterfaceOrientationPortrait</string> <key>UIStatusBarStyle</key>
<string>UIInterfaceOrientationPortraitUpsideDown</string> <string>UIStatusBarStyleDefault</string>
</array> <key>UISupportedInterfaceOrientations</key>
<key>UISupportedInterfaceOrientations~ipad</key> <array>
<array> <string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortrait</string> <string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string> </array>
<string>UIInterfaceOrientationLandscapeLeft</string> <key>UISupportedInterfaceOrientations~ipad</key>
<string>UIInterfaceOrientationLandscapeRight</string> <array>
</array> <string>UIInterfaceOrientationPortrait</string>
<key>UIUserInterfaceStyle</key> <string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>Automatic</string> <string>UIInterfaceOrientationLandscapeLeft</string>
<key>UIViewControllerBasedStatusBarAppearance</key> <string>UIInterfaceOrientationLandscapeRight</string>
<false/> </array>
</dict> <key>UIUserInterfaceStyle</key>
</plist> <string>Automatic</string>
<key>UIViewControllerBasedStatusBarAppearance</key>
<false/>
</dict>
</plist>

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);
} }
}; };
@@ -73,4 +80,4 @@ export const useNotification = () => {
initializeAndSetup(); initializeAndSetup();
}, []); }, []);
}; };

View File

@@ -5,7 +5,6 @@ import AsyncStorage from '@react-native-async-storage/async-storage';
import CryptoES from "crypto-es"; import CryptoES from "crypto-es";
import { router } from "expo-router"; import { router } from "expo-router";
import { createContext, MutableRefObject, ReactNode, useCallback, useContext, useEffect, useRef, useState } from 'react'; import { createContext, MutableRefObject, ReactNode, useCallback, useContext, useEffect, useRef, useState } from 'react';
import { Platform } from 'react-native';
const AuthContext = createContext<{ const AuthContext = createContext<{
signIn: (arg0: string) => void; signIn: (arg0: string) => void;
@@ -57,13 +56,13 @@ export default function AuthProvider({ children }: { children: ReactNode }): Rea
const permission = await requestPermission() const permission = await requestPermission()
if (permission) { if (permission) {
try { try {
// COMING SOON // if (Platform.OS === 'android') {
if (Platform.OS === 'android') { const tokenDevice = await getToken()
const tokenDevice = await getToken() const register = await apiRegisteredToken({ user: hasil, token: String(tokenDevice) })
const register = await apiRegisteredToken({ user: hasil, token: String(tokenDevice) }) // }else{
}else{ // const tokenDevice = await getToken()
const register = await apiRegisteredToken({ user: hasil, token: "" }) // const register = await apiRegisteredToken({ user: hasil, token: String(tokenDevice) })
} // }
} catch (error) { } catch (error) {
console.error(error) console.error(error)
} finally { } finally {
@@ -82,13 +81,12 @@ export default function AuthProvider({ children }: { children: ReactNode }): Rea
const signOut = useCallback(async () => { const signOut = useCallback(async () => {
try { try {
const hasil = await decryptToken(String(tokenRef.current)) const hasil = await decryptToken(String(tokenRef.current))
// COMING SOON // if (Platform.OS === 'android') {
if (Platform.OS === 'android') { const token = await getToken()
const token = await getToken() const response = await apiUnregisteredToken({ user: hasil, token: String(token) })
const response = await apiUnregisteredToken({ user: hasil, token: String(token) }) // }else{
}else{ // const response = await apiUnregisteredToken({ user: hasil, token: "" })
const response = await apiUnregisteredToken({ user: hasil, token: "" }) // }
}
} catch (error) { } catch (error) {
console.error(error) console.error(error)
} finally { } finally {