Merge pull request 'amalia/23-jan-26' (#10) from amalia/23-jan-26 into join

Reviewed-on: http://wibugit.wibudev.com/wibu/mobile-darmasaba/pulls/10
This commit is contained in:
2026-01-23 17:33:31 +08:00
14 changed files with 77 additions and 28 deletions

View File

@@ -30,7 +30,7 @@ export default function DetailAnnouncement() {
const { token, decryptToken } = useAuthSession() const { token, decryptToken } = useAuthSession()
const [data, setData] = useState<Props>({ id: '', title: '', desc: '' }) const [data, setData] = useState<Props>({ id: '', title: '', desc: '' })
const [dataMember, setDataMember] = useState<any>({}) const [dataMember, setDataMember] = useState<any>({})
const [dataFile, setDataFile] = useState<{ id:string; idStorage: string; name: string; extension: string }[]>([]) const [dataFile, setDataFile] = useState<{ id: string; idStorage: string; name: string; extension: string }[]>([])
const update = useSelector((state: any) => state.announcementUpdate) const update = useSelector((state: any) => state.announcementUpdate)
const entityUser = useSelector((state: any) => state.user) const entityUser = useSelector((state: any) => state.user)
const contentWidth = Dimensions.get('window').width const contentWidth = Dimensions.get('window').width
@@ -149,8 +149,8 @@ export default function DetailAnnouncement() {
: :
<> <>
<View style={[Styles.rowItemsCenter, { alignItems: 'flex-start' }]}> <View style={[Styles.rowItemsCenter, { alignItems: 'flex-start' }]}>
<MaterialIcons name="campaign" size={30} color="black" style={Styles.mr05} /> <MaterialIcons name="campaign" size={25} color="black" style={[Styles.mr05]} />
<Text style={[Styles.textDefaultSemiBold, Styles.w90]}>{data?.title}</Text> <Text style={[Styles.textDefaultSemiBold, Styles.w90, Styles.mt02]}>{data?.title}</Text>
</View> </View>
<View style={[Styles.mt10]}> <View style={[Styles.mt10]}>
{ {

View File

@@ -18,6 +18,7 @@ import { router, Stack } from "expo-router"
import * as Sharing from 'expo-sharing' import * as Sharing from 'expo-sharing'
import { useState } from "react" import { useState } from "react"
import { Alert, Image, Platform, RefreshControl, SafeAreaView, ScrollView, View } from "react-native" import { Alert, Image, Platform, RefreshControl, SafeAreaView, ScrollView, View } from "react-native"
import ImageViewing from 'react-native-image-viewing'
import * as mime from 'react-native-mime-types' import * as mime from 'react-native-mime-types'
import Toast from "react-native-toast-message" import Toast from "react-native-toast-message"
import { useDispatch, useSelector } from "react-redux" import { useDispatch, useSelector } from "react-redux"
@@ -38,12 +39,13 @@ export default function BannerList() {
const dispatch = useDispatch() const dispatch = useDispatch()
const [refreshing, setRefreshing] = useState(false) const [refreshing, setRefreshing] = useState(false)
const [loadingOpen, setLoadingOpen] = useState(false) const [loadingOpen, setLoadingOpen] = useState(false)
const [viewImg, setViewImg] = useState(false)
const handleDeleteEntity = async () => { const handleDeleteEntity = async () => {
try { try {
const hasil = await decryptToken(String(token?.current)); const hasil = await decryptToken(String(token?.current));
const deletedEntity = await apiDeleteBanner({ user: hasil }, dataId); const deletedEntity = await apiDeleteBanner({ user: hasil }, dataId);
if (deletedEntity.success ) { if (deletedEntity.success) {
Toast.show({ type: 'small', text1: 'Berhasil menghapus data', }) Toast.show({ type: 'small', text1: 'Berhasil menghapus data', })
apiGetBanner({ user: hasil }).then((data) => apiGetBanner({ user: hasil }).then((data) =>
dispatch(setEntities(data.data)) dispatch(setEntities(data.data))
@@ -167,8 +169,14 @@ export default function BannerList() {
/> />
<MenuItemRow <MenuItemRow
icon={<MaterialCommunityIcons name="file-eye" color="black" size={25} />} icon={<MaterialCommunityIcons name="file-eye" color="black" size={25} />}
title="Lihat / Share" title="Lihat"
onPress={() => { openFile() }} onPress={() => {
setModal(false)
setTimeout(() => {
setViewImg(true);
}, 1000);
// openFile()
}}
/> />
<MenuItemRow <MenuItemRow
icon={<Ionicons name="trash" color="black" size={25} />} icon={<Ionicons name="trash" color="black" size={25} />}
@@ -184,6 +192,14 @@ export default function BannerList() {
/> />
</View> </View>
</DrawerBottom> </DrawerBottom>
<ImageViewing
images={[{ uri: `${ConstEnv.url_storage}/files/${selectFile?.image}` }]}
imageIndex={0}
visible={viewImg}
onRequestClose={() => setViewImg(false)}
doubleTapToZoomEnabled
/>
</SafeAreaView> </SafeAreaView>
) )
} }

View File

@@ -328,7 +328,7 @@ export default function EditProfile() {
type="numeric" type="numeric"
placeholder="8XX-XXX-XXX" placeholder="8XX-XXX-XXX"
required required
itemLeft={<Text>+62</Text>} itemLeft={<Text style={[Platform.OS === 'ios' && Styles.mt02]}>+62</Text>}
value={data?.phone} value={data?.phone}
error={error.phone} error={error.phone}
errorText="Nomor Telepon tidak valid" errorText="Nomor Telepon tidak valid"

View File

@@ -5,13 +5,15 @@ import LabelStatus from "@/components/labelStatus";
import HeaderRightMemberDetail from "@/components/member/headerMemberDetail"; import HeaderRightMemberDetail from "@/components/member/headerMemberDetail";
import Skeleton from "@/components/skeleton"; import Skeleton from "@/components/skeleton";
import Text from "@/components/Text"; import Text from "@/components/Text";
import { assetUserImage } from "@/constants/AssetsError";
import { ConstEnv } from "@/constants/ConstEnv"; import { ConstEnv } from "@/constants/ConstEnv";
import { valueRoleUser } from "@/constants/RoleUser"; import { valueRoleUser } from "@/constants/RoleUser";
import Styles from "@/constants/Styles"; import Styles from "@/constants/Styles";
import { apiGetProfile } from "@/lib/api"; import { apiGetProfile } from "@/lib/api";
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 { RefreshControl, SafeAreaView, ScrollView, View } from "react-native"; import { Pressable, RefreshControl, SafeAreaView, ScrollView, View } from "react-native";
import ImageViewing from 'react-native-image-viewing';
import Toast from "react-native-toast-message"; import Toast from "react-native-toast-message";
import { useSelector } from "react-redux"; import { useSelector } from "react-redux";
@@ -32,12 +34,13 @@ type Props = {
export default function MemberDetail() { export default function MemberDetail() {
const { id } = useLocalSearchParams<{ id: string }>(); const { id } = useLocalSearchParams<{ id: string }>();
const [data, setData] = useState<Props>() const [data, setData] = useState<Props>()
const [error, setError] = useState(false) const [errorImg, setErrorImg] = useState(false)
const entityUser = useSelector((state: any) => state.user) const entityUser = useSelector((state: any) => state.user)
const [isEdit, setEdit] = useState(true) const [isEdit, setEdit] = useState(true)
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 [refreshing, setRefreshing] = useState(false) const [refreshing, setRefreshing] = useState(false)
const [preview, setPreview] = useState(false)
async function handleLoad(loading: boolean) { async function handleLoad(loading: boolean) {
try { try {
@@ -100,7 +103,9 @@ export default function MemberDetail() {
</> </>
: :
<> <>
<ImageUser src={`${ConstEnv.url_storage}/files/${data?.img}`} size="lg" /> <Pressable onPress={() => setPreview(true)}>
<ImageUser src={`${ConstEnv.url_storage}/files/${data?.img}`} size="lg" onError={setErrorImg} />
</Pressable>
<Text style={[Styles.textSubtitle, Styles.cWhite, Styles.mt10, { textAlign: 'center' }]}>{data?.name}</Text> <Text style={[Styles.textSubtitle, Styles.cWhite, Styles.mt10, { textAlign: 'center' }]}>{data?.name}</Text>
<Text style={[Styles.textMediumNormal, Styles.cWhite]}>{data?.role}</Text> <Text style={[Styles.textMediumNormal, Styles.cWhite]}>{data?.role}</Text>
</> </>
@@ -136,6 +141,14 @@ export default function MemberDetail() {
</View> </View>
</ScrollView> </ScrollView>
<ImageViewing
images={[{ uri: errorImg ? assetUserImage.uri : `${ConstEnv.url_storage}/files/${data?.img}` }]}
imageIndex={0}
visible={preview}
onRequestClose={() => setPreview(false)}
doubleTapToZoomEnabled
/>
</SafeAreaView> </SafeAreaView>
) )
} }

View File

@@ -331,7 +331,7 @@ export default function CreateMember() {
type="numeric" type="numeric"
placeholder="8XX-XXX-XXX" placeholder="8XX-XXX-XXX"
required required
itemLeft={<Text>+62</Text>} itemLeft={<Text style={[Platform.OS === 'ios' && Styles.mt02]}>+62</Text>}
error={error.phone} error={error.phone}
errorText="Nomor Telepon tidak valid" errorText="Nomor Telepon tidak valid"
onChange={val => { onChange={val => {

View File

@@ -371,7 +371,7 @@ export default function EditMember() {
type="numeric" type="numeric"
placeholder="8XX-XXX-XXX" placeholder="8XX-XXX-XXX"
required required
itemLeft={<Text>+62</Text>} itemLeft={<Text style={[Platform.OS === 'ios' && Styles.mt02]}>+62</Text>}
value={data?.phone} value={data?.phone}
error={error.phone} error={error.phone}
errorText="Nomor Telepon tidak valid" errorText="Nomor Telepon tidak valid"

View File

@@ -3,19 +3,22 @@ import ButtonBackHeader from "@/components/buttonBackHeader";
import { ButtonHeader } from "@/components/buttonHeader"; import { ButtonHeader } from "@/components/buttonHeader";
import ItemDetailMember from "@/components/itemDetailMember"; import ItemDetailMember from "@/components/itemDetailMember";
import Text from "@/components/Text"; import Text from "@/components/Text";
import { assetUserImage } from "@/constants/AssetsError";
import { ConstEnv } from "@/constants/ConstEnv"; import { ConstEnv } from "@/constants/ConstEnv";
import Styles from "@/constants/Styles"; import Styles from "@/constants/Styles";
import { useAuthSession } from "@/providers/AuthProvider"; import { useAuthSession } from "@/providers/AuthProvider";
import { AntDesign } from "@expo/vector-icons"; import { AntDesign } from "@expo/vector-icons";
import { router, Stack } from "expo-router"; import { router, Stack } from "expo-router";
import { useState } from "react"; import { useState } from "react";
import { Image, SafeAreaView, ScrollView, View } from "react-native"; import { Image, Pressable, SafeAreaView, ScrollView, View } from "react-native";
import ImageViewing from 'react-native-image-viewing';
import { useSelector } from 'react-redux'; import { useSelector } from 'react-redux';
export default function Profile() { export default function Profile() {
const { signOut } = useAuthSession() const { signOut } = useAuthSession()
const entities = useSelector((state: any) => state.entities) const entities = useSelector((state: any) => state.entities)
const [error, setError] = useState(false) const [error, setError] = useState(false)
const [preview, setPreview] = useState(false)
return ( return (
@@ -41,11 +44,13 @@ export default function Profile() {
<ScrollView style={[Styles.h100]}> <ScrollView style={[Styles.h100]}>
<View style={{ flexDirection: 'column' }}> <View style={{ flexDirection: 'column' }}>
<View style={[Styles.wrapHeadViewMember]}> <View style={[Styles.wrapHeadViewMember]}>
<Image <Pressable onPress={() => setPreview(true)}>
source={error ? require("../../assets/images/user.jpg") : { uri: `${ConstEnv.url_storage}/files/${entities.img}` }} <Image
onError={() => { setError(true) }} source={error ? require("../../assets/images/user.jpg") : { uri: `${ConstEnv.url_storage}/files/${entities.img}` }}
style={[Styles.userProfileBig]} onError={() => { setError(true) }}
/> style={[Styles.userProfileBig]}
/>
</Pressable>
<Text style={[Styles.textSubtitle, Styles.cWhite, Styles.mt10]}>{entities.name}</Text> <Text style={[Styles.textSubtitle, Styles.cWhite, Styles.mt10]}>{entities.name}</Text>
<Text style={[Styles.textMediumNormal, Styles.cWhite]}>{entities.role}</Text> <Text style={[Styles.textMediumNormal, Styles.cWhite]}>{entities.role}</Text>
</View> </View>
@@ -65,6 +70,13 @@ export default function Profile() {
</View> </View>
</View> </View>
</ScrollView> </ScrollView>
<ImageViewing
images={[{ uri: error ? assetUserImage.uri : `${ConstEnv.url_storage}/files/${entities.img}` }]}
imageIndex={0}
visible={preview}
onRequestClose={() => setPreview(false)}
doubleTapToZoomEnabled
/>
</SafeAreaView> </SafeAreaView>
) )
} }

BIN
bun.lockb

Binary file not shown.

View File

@@ -39,10 +39,10 @@ export default function ViewLogin({ onValidate }: Props) {
} }
} }
} else { } else {
return Toast.show({ type: 'small', text1: response.message, position: 'top' }) return Toast.show({ type: 'small', text1: response.message, position: 'bottom' })
} }
} catch (error) { } catch (error) {
return Toast.show({ type: 'small', text1: `Terjadi kesalahan, coba lagi`, position: 'top' }) return Toast.show({ type: 'small', text1: `Terjadi kesalahan, coba lagi`, position: 'bottom' })
} finally { } finally {
setLoadingLogin(false) setLoadingLogin(false)
} }
@@ -70,7 +70,7 @@ export default function ViewLogin({ onValidate }: Props) {
type="numeric" type="numeric"
placeholder="XXX-XXX-XXXX" placeholder="XXX-XXX-XXXX"
round round
itemLeft={<Text>+62</Text>} itemLeft={<Text style={[Platform.OS === 'ios' && Styles.mt02]}>+62</Text>}
info="Kami akan mengirim kode verifikasi melalui WhatsApp, guna mengonfirmasikan nomor Anda." /> info="Kami akan mengirim kode verifikasi melalui WhatsApp, guna mengonfirmasikan nomor Anda." />
<ButtonForm <ButtonForm
text="MASUK" text="MASUK"

View File

@@ -28,7 +28,7 @@ export default function ViewVerification({ phone, otp }: Props) {
const encrypted = await encryptToken(valueUser); const encrypted = await encryptToken(valueUser);
signIn(encrypted); signIn(encrypted);
} else { } else {
return Toast.show({ type: 'small', text1: 'Terjadi kesalahan', position: 'top' }) return Toast.show({ type: 'small', text1: 'Terjadi kesalahan', position: 'bottom' })
} }
} }
@@ -36,7 +36,7 @@ export default function ViewVerification({ phone, otp }: Props) {
if (value === otpFix.toString()) { if (value === otpFix.toString()) {
login() login()
} else { } else {
return Toast.show({ type: 'small', text1: 'Kode OTP tidak sesuai', position: 'top' }); return Toast.show({ type: 'small', text1: 'Kode OTP tidak sesuai', position: 'bottom' });
} }
} }

View File

@@ -6,17 +6,19 @@ type Props = {
src: string, src: string,
size?: 'sm' | 'xs' | 'lg' size?: 'sm' | 'xs' | 'lg'
border?: boolean border?: boolean
onError?: (val:boolean) => void
} }
export default function ImageUser({ src, size }: Props) { export default function ImageUser({ src, size, onError }: Props) {
const [error, setError] = useState(false) const [error, setError] = useState(false)
return ( return (
<Image <Image
source={error ? require('../assets/images/user.jpg') : { uri: src }} source={error ? require('../assets/images/user.jpg') : { uri: src }}
style={[size == 'xs' ? Styles.userProfileExtraSmall : size == 'lg' ? Styles.userProfileBig : Styles.userProfileSmall, Styles.borderAll]} style={[size == 'xs' ? Styles.userProfileExtraSmall : size == 'lg' ? Styles.userProfileBig : Styles.userProfileSmall, Styles.borderAll]}
onError={() => onError={() => {
setError(true) setError(true)
} onError?.(true)
}}
/> />
) )
} }

5
constants/AssetsError.ts Normal file
View File

@@ -0,0 +1,5 @@
import { Image } from "react-native";
export const assetUserImage = Image.resolveAssetSource(
require('@/assets/images/user.jpg')
);

View File

@@ -394,7 +394,7 @@
); );
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG"; OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG";
PRODUCT_BUNDLE_IDENTIFIER = mobiledarmasaba.app; PRODUCT_BUNDLE_IDENTIFIER = mobiledarmasaba.app;
PRODUCT_NAME = "Desa"; PRODUCT_NAME = Desa;
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_OBJC_BRIDGING_HEADER = "Desa/Desa-Bridging-Header.h"; SWIFT_OBJC_BRIDGING_HEADER = "Desa/Desa-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_OPTIMIZATION_LEVEL = "-Onone";
@@ -429,7 +429,7 @@
); );
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE"; OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
PRODUCT_BUNDLE_IDENTIFIER = mobiledarmasaba.app; PRODUCT_BUNDLE_IDENTIFIER = mobiledarmasaba.app;
PRODUCT_NAME = "Desa"; PRODUCT_NAME = Desa;
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_OBJC_BRIDGING_HEADER = "Desa/Desa-Bridging-Header.h"; SWIFT_OBJC_BRIDGING_HEADER = "Desa/Desa-Bridging-Header.h";
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;

View File

@@ -75,6 +75,7 @@
"react-native-gesture-handler": "~2.24.0", "react-native-gesture-handler": "~2.24.0",
"react-native-gifted-charts": "^1.4.57", "react-native-gifted-charts": "^1.4.57",
"react-native-image-picker": "^8.2.1", "react-native-image-picker": "^8.2.1",
"react-native-image-viewing": "^0.2.2",
"react-native-mime-types": "^2.5.0", "react-native-mime-types": "^2.5.0",
"react-native-modal": "^14.0.0-rc.1", "react-native-modal": "^14.0.0-rc.1",
"react-native-modal-datetime-picker": "^18.0.0", "react-native-modal-datetime-picker": "^18.0.0",