Merge pull request 'amalia/03-okt-25' (#45) from amalia/03-okt-25 into join

Reviewed-on: bip/mobile-darmasaba#45
This commit is contained in:
2025-10-03 17:31:32 +08:00
11 changed files with 170 additions and 129 deletions

View File

@@ -4,7 +4,7 @@ export default {
expo: {
name: "Desa+",
slug: "mobile-darmasaba",
version: "1.0.5", // Versi aplikasi (App Store)
version: "2.0.1", // Versi aplikasi (App Store)
jsEngine: "jsc",
orientation: "portrait",
icon: "./assets/images/logo-icon-small.png",
@@ -14,7 +14,7 @@ export default {
ios: {
supportsTablet: true,
bundleIdentifier: "mobiledarmasaba.app",
buildNumber: "2",
buildNumber: "3",
infoPlist: {
ITSAppUsesNonExemptEncryption: false,
CFBundleDisplayName: "Desa+"
@@ -23,7 +23,7 @@ export default {
},
android: {
package: "mobiledarmasaba.app",
versionCode: 9,
versionCode: 10,
adaptiveIcon: {
foregroundImage: "./assets/images/logo-icon-small.png",
backgroundColor: "#ffffff"

View File

@@ -45,6 +45,8 @@ export default function AddMemberDivision() {
setData(responsemember.data.filter((i: any) => i.idUserRole != 'supadmin'))
} catch (error) {
console.error(error)
} finally {
setLoading(false)
}
}

View File

@@ -16,7 +16,7 @@ import { useAuthSession } from "@/providers/AuthProvider"
import { Feather, MaterialCommunityIcons, MaterialIcons } from "@expo/vector-icons"
import { router, Stack, useLocalSearchParams } from "expo-router"
import { useEffect, useState } from "react"
import { Pressable, SafeAreaView, ScrollView, View } from "react-native"
import { Pressable, RefreshControl, SafeAreaView, ScrollView, View } from "react-native"
import Toast from "react-native-toast-message"
import { useSelector } from "react-redux"
@@ -39,6 +39,7 @@ type PropsMember = {
}
export default function InformationDivision() {
const [refreshing, setRefreshing] = useState(false)
const entityUser = useSelector((state: any) => state.user)
const { id } = useLocalSearchParams<{ id: string }>()
const [isModal, setModal] = useState(false)
@@ -116,6 +117,14 @@ export default function InformationDivision() {
}
}
const handleRefresh = async () => {
setRefreshing(true)
handleLoad(false)
handleCheckMember()
await new Promise(resolve => setTimeout(resolve, 2000));
setRefreshing(false)
};
async function handleCheckMember() {
try {
const hasil = await decryptToken(String(token?.current));
@@ -161,7 +170,15 @@ export default function InformationDivision() {
headerRight: () => ((entityUser.role != "user" && entityUser.role != "coadmin") || isAdminDivision) && <HeaderRightDivisionInfo id={id} active={dataDetail?.isActive} />,
}}
/>
<ScrollView style={[Styles.h100]}>
<ScrollView
refreshControl={
<RefreshControl
refreshing={refreshing}
onRefresh={handleRefresh}
/>
}
style={[Styles.h100]}
>
<View style={[Styles.p15]}>
{
dataDetail?.isActive == false && (

View File

@@ -8,6 +8,7 @@ import { ConstEnv } from "@/constants/ConstEnv";
import Styles from "@/constants/Styles";
import { apiEditProfile, apiGetProfile } from "@/lib/api";
import { setEntities } from "@/lib/entitiesSlice";
import { validateName } from "@/lib/fun_validateName";
import { useAuthSession } from "@/providers/AuthProvider";
import { MaterialCommunityIcons } from "@expo/vector-icons";
import { useHeaderHeight } from "@react-navigation/elements";
@@ -109,7 +110,7 @@ export default function EditProfile() {
}
} else if (cat == "name") {
setData({ ...data, name: val });
if (val == "") {
if (!validateName(val)) {
setError({ ...error, name: true });
} else {
setError({ ...error, name: false });
@@ -164,8 +165,8 @@ export default function EditProfile() {
if (imgForm != undefined) {
fd.append("file", {
uri: imgForm.uri,
type: imgForm.mimeType,
name: imgForm.fileName,
type: imgForm.mimeType || "image/jpeg",
name: imgForm.fileName || "image.jpg",
} as any);
} else {
fd.append("file", "undefined",);
@@ -197,9 +198,9 @@ export default function EditProfile() {
const pickImageAsync = async () => {
let result = await ImagePicker.launchImageLibraryAsync({
mediaTypes: ["images"],
allowsEditing: false,
quality: 1,
aspect: [1, 1],
allowsEditing: true,
quality: 0.9,
aspect: [1, 1]
});
if (!result.canceled) {
@@ -305,7 +306,7 @@ export default function EditProfile() {
required
value={data?.name}
error={error.name}
errorText="Nama tidak boleh kosong"
errorText="Nama harus 350 karakter (huruf, angka, spasi, dan simbol ringan (. , ' _ -))"
onChange={val => {
validationForm("name", val)
}}

View File

@@ -7,6 +7,7 @@ import Text from "@/components/Text";
import { ColorsStatus } from "@/constants/ColorsStatus";
import Styles from "@/constants/Styles";
import { apiCreateUser } from "@/lib/api";
import { validateName } from "@/lib/fun_validateName";
import { setUpdateMember } from "@/lib/memberSlice";
import { useAuthSession } from "@/providers/AuthProvider";
import { MaterialCommunityIcons } from "@expo/vector-icons";
@@ -100,7 +101,7 @@ export default function CreateMember() {
}
} else if (cat == "name") {
setDataForm({ ...dataForm, name: val });
if (val == "") {
if (!validateName(val)) {
setError({ ...error, name: true });
} else {
setError({ ...error, name: false });
@@ -166,8 +167,8 @@ export default function CreateMember() {
if (imgForm != undefined) {
fd.append("file", {
uri: imgForm.uri,
type: imgForm.mimeType,
name: imgForm.fileName,
type: imgForm.mimeType || "image/jpeg",
name: imgForm.fileName || "image.jpg",
} as any)
} else {
fd.append("file", "undefined")
@@ -193,9 +194,9 @@ export default function CreateMember() {
const pickImageAsync = async () => {
let result = await ImagePicker.launchImageLibraryAsync({
mediaTypes: ["images"],
allowsEditing: false,
quality: 1,
aspect: [1, 1],
allowsEditing: true,
quality: 0.9,
aspect: [1, 1]
});
if (!result.canceled) {
@@ -309,7 +310,7 @@ export default function CreateMember() {
placeholder="Nama"
required
error={error.name}
errorText="Nama tidak boleh kosong"
errorText="Nama harus 350 karakter (huruf, angka, spasi, dan simbol ringan (. , ' _ -))"
onChange={val => {
validationForm("name", val)
}}

View File

@@ -7,6 +7,7 @@ import Text from "@/components/Text";
import { ConstEnv } from "@/constants/ConstEnv";
import Styles from "@/constants/Styles";
import { apiEditUser, apiGetProfile } from "@/lib/api";
import { validateName } from "@/lib/fun_validateName";
import { setUpdateMember } from "@/lib/memberSlice";
import { useAuthSession } from "@/providers/AuthProvider";
import { MaterialCommunityIcons } from "@expo/vector-icons";
@@ -132,7 +133,7 @@ export default function EditMember() {
}
} else if (cat == "name") {
setData({ ...data, name: val });
if (val == "") {
if (!validateName(val)) {
setError({ ...error, name: true });
} else {
setError({ ...error, name: false });
@@ -187,8 +188,8 @@ export default function EditMember() {
if (imgForm != undefined) {
fd.append("file", {
uri: imgForm.uri,
type: imgForm.mimeType,
name: imgForm.fileName,
type: imgForm.mimeType || "image/jpeg",
name: imgForm.fileName || "image.jpg",
} as any);
} else {
fd.append("file", "undefined",);
@@ -220,9 +221,9 @@ export default function EditMember() {
const pickImageAsync = async () => {
let result = await ImagePicker.launchImageLibraryAsync({
mediaTypes: ["images"],
allowsEditing: false,
quality: 1,
aspect: [1, 1],
allowsEditing: true,
quality: 0.9,
aspect: [1, 1]
});
if (!result.canceled) {
@@ -348,7 +349,7 @@ export default function EditMember() {
required
value={data?.name}
error={error.name}
errorText="Nama tidak boleh kosong"
errorText="Nama harus 350 karakter (huruf, angka, spasi, dan simbol ringan (. , ' _ -))"
onChange={val => {
validationForm("name", val)
}}

View File

@@ -104,6 +104,7 @@ export function InputDate({ label, value, placeholder, onChange, info, disable,
display="spinner"
onChange={(event, date) => { onChangeDate(event.type, date) }}
onTouchCancel={() => setModal(false)}
textColor="black"
/>
</ModalFloat>
)

View File

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

View File

@@ -1,8 +1,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">
<plist version="1.0">
<dict>
<dict>
<key>aps-environment</key>
<string>development</string>
</dict>
</dict>
</plist>

View File

@@ -1,7 +1,7 @@
<?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">
<plist version="1.0">
<dict>
<dict>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true/>
<key>CFBundleDevelopmentRegion</key>
@@ -19,7 +19,7 @@
<key>CFBundlePackageType</key>
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleShortVersionString</key>
<string>1.0.5</string>
<string>2.0.1</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleURLTypes</key>
@@ -39,7 +39,7 @@
</dict>
</array>
<key>CFBundleVersion</key>
<string>2</string>
<string>3</string>
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<key>LSMinimumSystemVersion</key>
@@ -95,5 +95,5 @@
<string>Automatic</string>
<key>UIViewControllerBasedStatusBarAppearance</key>
<false/>
</dict>
</dict>
</plist>

18
lib/fun_validateName.ts Normal file
View File

@@ -0,0 +1,18 @@
/**
* Validasi Display Name
* Aturan:
* - 2 sampai 50 karakter
* - Huruf, angka, spasi, titik, koma, apostrof, underscore, dan dash
* - Tidak boleh semua spasi
*/
export const validateName = (name: string): boolean => {
const trimmed = name.trim();
// Jika kosong setelah di-trim → invalid
if (!trimmed) return false;
// Regex: hanya huruf, angka, spasi, titik, koma, apostrof, underscore, dash
const regex = /^[a-zA-Z0-9\s._,'-]{3,50}$/;
return regex.test(trimmed);
};