From 9756a05d2a986507b613fa0450a924873710bd46 Mon Sep 17 00:00:00 2001 From: amel Date: Mon, 5 May 2025 12:15:24 +0800 Subject: [PATCH] upd: member Deskripsi: - detail member - aktivasi member - edit member No Issues --- app/(application)/member/[id].tsx | 61 +++- app/(application)/member/edit/[id].tsx | 366 ++++++++++++++++++++--- components/itemDetailMember.tsx | 2 +- components/member/headerMemberDetail.tsx | 31 +- components/modalSelect.tsx | 6 +- lib/api.ts | 20 +- 6 files changed, 422 insertions(+), 64 deletions(-) diff --git a/app/(application)/member/[id].tsx b/app/(application)/member/[id].tsx index 5cb84b0..5517f05 100644 --- a/app/(application)/member/[id].tsx +++ b/app/(application)/member/[id].tsx @@ -1,12 +1,48 @@ import ButtonBackHeader from "@/components/buttonBackHeader"; import ItemDetailMember from "@/components/itemDetailMember"; import HeaderRightMemberDetail from "@/components/member/headerMemberDetail"; +import { valueRoleUser } from "@/constants/RoleUser"; import Styles from "@/constants/Styles"; +import { apiGetProfile } from "@/lib/api"; import { router, Stack, useLocalSearchParams } from "expo-router"; +import { useEffect, useState } from "react"; import { Image, SafeAreaView, ScrollView, Text, View } from "react-native"; +import { useSelector } from "react-redux"; + +type Props = { + id: string, + name: string, + nik: string, + email: string, + phone: string, + gender: string, + position: string, + group: string, + img: string, + isActive: boolean, + role: string +} export default function MemberDetail() { - const { id } = useLocalSearchParams(); + const { id } = useLocalSearchParams<{ id: string }>(); + const [data, setData] = useState() + const [error, setError] = useState(false) + const entityUser = useSelector((state: any) => state.user) + const [isEdit, setEdit] = useState(false) + + async function handleLoad() { + try { + const response = await apiGetProfile({ id: id }) + setData(response.data) + setEdit(valueRoleUser.filter((v) => v.login == entityUser.role)[0]?.data.some((i: any) => i.id == response.data.idUserRole)) + } catch (error) { + console.error(error) + } + } + + useEffect(() => { + handleLoad() + }, []); return ( @@ -14,8 +50,8 @@ export default function MemberDetail() { options={{ headerLeft: () => { router.back() }} />, headerTitle: 'Anggota', - headerTitleAlign:'center', - headerRight: () => , + headerTitleAlign: 'center', + headerRight: () => (entityUser.role != "user") && isEdit && , headerShadowVisible: false }} /> @@ -23,22 +59,23 @@ export default function MemberDetail() { { setError(true) }} style={[Styles.userProfileBig]} /> - Putri Ayu Dewi - Super Admin + {data?.name} + {data?.role} Informasi - - - - - - + + + + + + diff --git a/app/(application)/member/edit/[id].tsx b/app/(application)/member/edit/[id].tsx index 02c57c8..201addc 100644 --- a/app/(application)/member/edit/[id].tsx +++ b/app/(application)/member/edit/[id].tsx @@ -1,22 +1,194 @@ -import ButtonBackHeader from "@/components/buttonBackHeader" -import ButtonSaveHeader from "@/components/buttonSaveHeader" -import { InputForm } from "@/components/inputForm" -import SelectForm from "@/components/selectForm" -import { ColorsStatus } from "@/constants/ColorsStatus" -import Styles from "@/constants/Styles" -import { MaterialCommunityIcons } from "@expo/vector-icons" -import * as ImagePicker from 'expo-image-picker' -import { router, Stack } from "expo-router" -import { useState } from "react" -import { Image, Pressable, SafeAreaView, ScrollView, Text, View } from "react-native" +import ButtonBackHeader from "@/components/buttonBackHeader"; +import ButtonSaveHeader from "@/components/buttonSaveHeader"; +import { InputForm } from "@/components/inputForm"; +import ModalSelect from "@/components/modalSelect"; +import SelectForm from "@/components/selectForm"; +import Styles from "@/constants/Styles"; +import { apiEditUser, apiGetProfile } from "@/lib/api"; +import { useAuthSession } from "@/providers/AuthProvider"; +import * as ImagePicker from "expo-image-picker"; +import { router, Stack, useLocalSearchParams } from "expo-router"; +import { useEffect, useState } from "react"; +import { + Image, + Pressable, + SafeAreaView, + ScrollView, + Text, + ToastAndroid, + View, +} from "react-native"; + +type Props = { + id: string; + name: string; + nik: string; + email: string; + phone: string; + gender: string; + img: string; + isActive: boolean; + idGroup: string; + idPosition: string; + idUserRole: string; +}; export default function EditMember() { - const [chooseGroup, setChooseGroup] = useState({ val: '', label: '' }) + const { token, decryptToken } = useAuthSession() + const { id } = useLocalSearchParams<{ id: string }>(); + const [errorImg, setErrorImg] = useState(false) const [selectedImage, setSelectedImage] = useState(undefined); + const [choosePosition, setChoosePosition] = useState({ val: "", label: "" }); + const [chooseRole, setChooseRole] = useState({ val: "", label: "" }); + const [chooseGender, setChooseGender] = useState({ val: "", label: "" }); + const [valSelect, setValSelect] = useState<"group" | "position" | "role" | "gender">("group"); + const [isSelect, setSelect] = useState(false); + const [disableBtn, setDisableBtn] = useState(false) + const [valChoose, setValChoose] = useState("") + const [data, setData] = useState({ + id: "", + name: "", + nik: "", + email: "", + phone: "", + gender: "", + img: "", + isActive: false, + idGroup: "", + idPosition: "", + idUserRole: "", + }); + const [error, setError] = useState({ + position: false, + nik: false, + name: false, + phone: false, + email: false, + gender: false, + role: false, + }); + + async function handleLoad() { + try { + const response = await apiGetProfile({ id: id }); + setData(response.data); + setChoosePosition({ + val: response.data.idPosition, + label: response.data.position, + }); + setChooseRole({ + val: response.data.idUserRole, + label: response.data.role, + }); + setChooseGender({ + val: response.data.gender, + label: response.data.gender == "M" ? "Laki-laki" : "Perempuan", + }) + } catch (error) { + console.error(error); + } + } + + useEffect(() => { + handleLoad(); + }, []); + + function validationForm(cat: string, val: any, label?: string) { + if (cat == "position") { + setChoosePosition({ val, label: String(label) }); + setData({ ...data, idPosition: val }); + if (val == "" || val == "null") { + setError({ ...error, position: true }); + } else { + setError({ ...error, position: false }); + } + } else if (cat == "role") { + setChooseRole({ val, label: String(label) }); + setData({ ...data, idUserRole: val }); + if (val == "" || val == "null") { + setError({ ...error, role: true }); + } else { + setError({ ...error, role: false }); + } + } else if (cat == "nik") { + setData({ ...data, nik: val }); + if (val == "" || val.length !== 16) { + setError({ ...error, nik: true }); + } else { + setError({ ...error, nik: false }); + } + } else if (cat == "name") { + setData({ ...data, name: val }); + if (val == "") { + setError({ ...error, name: true }); + } else { + setError({ ...error, name: false }); + } + } else if (cat == "email") { + setData({ ...data, email: val }); + if ( + val == "" || + !/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/.test(val) + ) { + setError({ ...error, email: true }); + } else { + setError({ ...error, email: false }); + } + } else if (cat == "phone") { + setData({ ...data, phone: val }); + if (val == "" || !(val.length >= 10 && val.length <= 15)) { + setError({ ...error, phone: true }); + } else { + setError({ ...error, phone: false }); + } + } else if (cat == "gender") { + setChooseGender({ val, label: String(label) }); + setData({ ...data, gender: val }); + if (val == "" || val == "null") { + setError({ ...error, gender: true }); + } else { + setError({ ...error, gender: false }); + } + } + + } + + function checkForm() { + if (Object.values(error).some((v) => v == true) == true || Object.values(data).some((v) => v == "") == true) { + setDisableBtn(true) + } else { + setDisableBtn(false) + } + } + + useEffect(() => { + checkForm() + }, [error, data]) + + async function handleEdit() { + try { + const hasil = await decryptToken(String(token?.current)) + const fd = new FormData() + fd.append("data", JSON.stringify( + { user: hasil, ...data } + )) + const response = await apiEditUser(fd, id) + if (response.success) { + ToastAndroid.show('Berhasil mengupdate data', ToastAndroid.SHORT) + router.replace(`/member/${id}`); + } else { + ToastAndroid.show(response.message, ToastAndroid.SHORT) + } + } catch (error) { + console.error(error) + } + } + + const pickImageAsync = async () => { let result = await ImagePicker.launchImageLibraryAsync({ - mediaTypes: ['images'], + mediaTypes: ["images"], allowsEditing: true, quality: 1, }); @@ -24,7 +196,7 @@ export default function EditMember() { if (!result.canceled) { setSelectedImage(result.assets[0].uri); } else { - alert('Tidak ada gambar yang dipilih'); + alert("Tidak ada gambar yang dipilih"); } }; @@ -32,16 +204,39 @@ export default function EditMember() { { router.back() }} />, - headerTitle: 'Edit Anggota', - headerTitleAlign: 'center', - headerRight: () => { router.back() }} /> + headerLeft: () => ( + { + router.back(); + }} + /> + ), + headerTitle: "Edit Anggota", + headerTitleAlign: "center", + headerRight: () => ( + { + handleEdit() + }} + /> + ), }} /> - - { + + + { + { setErrorImg(true) }} + /> + } + + {/* { selectedImage != undefined ? ( @@ -51,29 +246,118 @@ export default function EditMember() { ) - } + } */} - { }} /> - { }} /> - - - - +62} /> - { }} /> - {/* { - AlertKonfirmasi({ - title: 'Konfirmasi', - desc: 'Apakah anda yakin ingin mengubah data?', - onPress: () => { - ToastAndroid.show('Berhasil mengubah data', ToastAndroid.SHORT) - router.back() - } - }) - }} /> */} + setValChoose(choosePosition.val); + setValSelect("position"); + setSelect(true); + }} + error={error.position} + errorText="Jabatan tidak boleh kosong" + /> + { + setValChoose(chooseRole.val); + setValSelect("role"); + setSelect(true); + }} + error={error.role} + errorText="Role tidak boleh kosong" + /> + { + validationForm("nik", val) + }} + /> + { + validationForm("name", val) + }} + /> + { + validationForm("email", val) + }} + /> + +62} + value={data?.phone} + error={error.phone} + errorText="Nomor Telepon tidak valid" + onChange={val => { + validationForm("phone", val) + }} + /> + { + setValChoose(chooseGender.val); + setValSelect("gender"); + setSelect(true); + }} + error={error.gender} + errorText="Jenis Kelamin tidak boleh kosong" + /> + + { + validationForm(valSelect, value.val, value.label); + }} + title={ + valSelect == "group" + ? "Lembaga Desa" + : valSelect == "position" + ? "Jabatan" + : valSelect == "role" + ? "User Role" + : "Jenis Kelamin" + } + open={isSelect} + idParent={valSelect == "position" ? data?.idGroup : ""} + valChoose={valChoose} + /> - ) -} \ No newline at end of file + ); +} diff --git a/components/itemDetailMember.tsx b/components/itemDetailMember.tsx index 2069fc7..e30ad64 100644 --- a/components/itemDetailMember.tsx +++ b/components/itemDetailMember.tsx @@ -4,7 +4,7 @@ import { Text, View } from "react-native"; type Props = { category: 'nik' | 'group' | 'position' | 'phone' | 'email' | 'gender' | 'dokumen' | 'type' | 'location' | 'owner' | 'calendar' | 'share' - value: string + value: string | undefined border?: boolean } diff --git a/components/member/headerMemberDetail.tsx b/components/member/headerMemberDetail.tsx index dace0ae..df7211f 100644 --- a/components/member/headerMemberDetail.tsx +++ b/components/member/headerMemberDetail.tsx @@ -1,20 +1,38 @@ import Styles from "@/constants/Styles" +import { apiDeleteUser } from "@/lib/api" +import { useAuthSession } from "@/providers/AuthProvider" import { MaterialCommunityIcons } from "@expo/vector-icons" +import { router } from "expo-router" import { useState } from "react" import { ToastAndroid, View } from "react-native" import AlertKonfirmasi from "../alertKonfirmasi" import ButtonMenuHeader from "../buttonMenuHeader" import DrawerBottom from "../drawerBottom" import MenuItemRow from "../menuItemRow" -import { router } from "expo-router" type Props = { - id: string | string[] + active: any, + id: string } -export default function HeaderRightMemberDetail({ id }: Props) { +export default function HeaderRightMemberDetail({ active, id }: Props) { + const { token, decryptToken } = useAuthSession() const [isVisible, setVisible] = useState(false) + async function handleActive() { + try { + const hasil = await decryptToken(String(token?.current)) + const response = await apiDeleteUser({ user: hasil, isActive: active }, id) + } catch (error) { + console.error(error) + } finally { + setVisible(false) + ToastAndroid.show('Berhasil mengupdate data', ToastAndroid.SHORT) + router.replace(`/member/${id}`); + } + + } + return ( <> { setVisible(true) }} /> @@ -22,14 +40,13 @@ export default function HeaderRightMemberDetail({ id }: Props) { } - title="Non Aktifkan" + title={active ? "Non Aktifkan" : "Aktifkan"} onPress={() => { AlertKonfirmasi({ title: 'Konfirmasi', - desc: 'Apakah anda yakin ingin menonaktifkan data?', + desc: active ? 'Apakah anda yakin ingin menonaktifkan user?' : 'Apakah anda yakin ingin mengaktifkan user?', onPress: () => { - setVisible(false) - ToastAndroid.show('Berhasil mengubah data', ToastAndroid.SHORT) + handleActive() } }) }} diff --git a/components/modalSelect.tsx b/components/modalSelect.tsx index c372698..0995f1a 100644 --- a/components/modalSelect.tsx +++ b/components/modalSelect.tsx @@ -17,14 +17,15 @@ type Props = { category: 'group' | 'status-task' | 'position' | 'role' | 'gender' idParent?: string onSelect: (value: { val: string, label: string }) => void + valChoose?: string } -export default function ModalSelect({ open, close, title, category, idParent, onSelect }: Props) { +export default function ModalSelect({ open, close, title, category, idParent, onSelect, valChoose }: Props) { const { token, decryptToken } = useAuthSession() const entityUser = useSelector((state: any) => state.user) const dispatch = useDispatch() const entitiesGroup = useSelector((state: any) => state.filterGroup) - const [chooseValue, setChooseValue] = useState({ val: '', label: '' }) + const [chooseValue, setChooseValue] = useState({ val: valChoose, label: '' }) const [data, setData] = useState([]) async function handleLoadGroup() { @@ -65,6 +66,7 @@ export default function ModalSelect({ open, close, title, category, idParent, on } else if (category == "gender") { handleLoadGender() } + setChooseValue({ ...chooseValue, val: valChoose }) }, [dispatch, open]); function onChoose(val: string, label: string) { diff --git a/lib/api.ts b/lib/api.ts index bd62116..d9b46db 100644 --- a/lib/api.ts +++ b/lib/api.ts @@ -127,4 +127,22 @@ export const apiCreateUser = async (data: FormData) => { }, }) return response.data; -}; \ No newline at end of file +}; + +export const apiDeleteUser = async (data: { user: string, isActive: boolean }, id: string) => { + await api.delete(`mobile/user/${id}`, { data }).then(response => { + return response.data; + }) + .catch(error => { + console.error('Error:', error); + }); +}; + +export const apiEditUser = async (data: FormData, id: string) => { + const response = await api.put(`/mobile/user/${id}`, data, { + headers: { + 'Content-Type': 'multipart/form-data', + }, + }) + return response.data; +};