diff --git a/app/(application)/member/create.tsx b/app/(application)/member/create.tsx index 0b01316..9bdb424 100644 --- a/app/(application)/member/create.tsx +++ b/app/(application)/member/create.tsx @@ -1,22 +1,168 @@ 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 { ColorsStatus } from "@/constants/ColorsStatus"; import Styles from "@/constants/Styles"; +import { apiCreateUser } from "@/lib/api"; +import { setUpdateMember } from "@/lib/memberSlice"; +import { useAuthSession } from "@/providers/AuthProvider"; import { MaterialCommunityIcons } from "@expo/vector-icons"; -import * as ImagePicker from 'expo-image-picker'; +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 { useEffect, useState } from "react"; +import { + Image, + Pressable, + SafeAreaView, + ScrollView, + Text, + ToastAndroid, + View, +} from "react-native"; +import { useDispatch, useSelector } from "react-redux"; export default function CreateMember() { - const [chooseGroup, setChooseGroup] = useState({ val: '', label: '' }) + const dispatch = useDispatch() + const update = useSelector((state: any) => state.positionUpdate) + const { token, decryptToken } = useAuthSession() + const [valSelect, setValSelect] = useState<"group" | "position" | "role" | "gender">("group"); + const [chooseGroup, setChooseGroup] = useState({ val: "", label: "" }); + const [choosePosition, setChoosePosition] = useState({ val: "", label: "" }); + const [chooseRole, setChooseRole] = useState({ val: "", label: "" }); + const [chooseGender, setChooseGender] = useState({ val: "", label: "" }); const [selectedImage, setSelectedImage] = useState(undefined); + const entityUser = useSelector((state: any) => state.user); + const [isSelect, setSelect] = useState(false); + const [disableBtn, setDisableBtn] = useState(true) + const [error, setError] = useState({ + group: false, + position: false, + nik: false, + name: false, + phone: false, + email: false, + gender: false, + role: false, + }); + const [dataForm, setDataForm] = useState({ + idGroup: "", + idPosition: "", + idUserRole: "", + nik: "", + name: "", + phone: "", + email: "", + gender: "", + }); + + function validationForm(cat: string, val: any, label?: string) { + if (cat == "group") { + setChooseGroup({ val, label: String(label) }); + setChoosePosition({ val: "", label: "" }); + setDataForm({ ...dataForm, idGroup: val, idPosition: "" }); + if (val == "" || val == "null") { + setError({ ...error, group: true }); + } else { + setError({ ...error, group: false }); + } + } else if (cat == "position") { + setChoosePosition({ val, label: String(label) }); + setDataForm({ ...dataForm, idPosition: val }); + if (val == "" || val == "null") { + setError({ ...error, position: true }); + } else { + setError({ ...error, position: false }); + } + } else if (cat == "role") { + setChooseRole({ val, label: String(label) }); + setDataForm({ ...dataForm, idUserRole: val }); + if (val == "" || val == "null") { + setError({ ...error, role: true }); + } else { + setError({ ...error, role: false }); + } + } else if (cat == "nik") { + setDataForm({ ...dataForm, nik: val }); + if (val == "" || val.length !== 16) { + setError({ ...error, nik: true }); + } else { + setError({ ...error, nik: false }); + } + } else if (cat == "name") { + setDataForm({ ...dataForm, name: val }); + if (val == "") { + setError({ ...error, name: true }); + } else { + setError({ ...error, name: false }); + } + } else if (cat == "email") { + setDataForm({ ...dataForm, 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") { + setDataForm({ ...dataForm, 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) }); + setDataForm({ ...dataForm, 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(dataForm).some((v) => v == "") == true) { + setDisableBtn(true) + } else { + setDisableBtn(false) + } + } + + useEffect(() => { + checkForm() + }, [error, dataForm]) + + + async function handleCreate() { + try { + const hasil = await decryptToken(String(token?.current)) + const fd = new FormData() + fd.append("data", JSON.stringify( + { user: hasil, ...dataForm } + )) + const response = await apiCreateUser(fd) + if (response.success) { + ToastAndroid.show('Berhasil menambahkan data', ToastAndroid.SHORT) + dispatch(setUpdateMember(!update)) + router.back() + } 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 +170,7 @@ export default function CreateMember() { if (!result.canceled) { setSelectedImage(result.assets[0].uri); } else { - alert('Tidak ada gambar yang dipilih'); + alert("Tidak ada gambar yang dipilih"); } }; @@ -32,49 +178,161 @@ export default function CreateMember() { { router.back() }} />, - headerTitle: 'Tambah Anggota', - headerTitleAlign: 'center', - headerRight: () => { router.push('/member') }} /> + headerLeft: () => ( + { + router.back(); + }} + /> + ), + headerTitle: "Tambah Anggota", + headerTitleAlign: "center", + headerRight: () => ( + { handleCreate() }} + /> + ), }} /> - - { - selectedImage != undefined ? ( - - - - ) : ( - - - - ) - } + + {selectedImage != undefined ? ( + + + + ) : ( + + + + )} - { }} /> - { }} /> - { }} /> - - - - +62} /> - { }} /> - {/* { + setValSelect("group"); + setSelect(true); + }} + error={error.group} + errorText="Lembaga Desa tidak boleh kosong" + /> + )} + { - AlertKonfirmasi({ - title: 'Konfirmasi', - desc: 'Apakah anda yakin ingin menambahkan data?', - onPress: () => { - ToastAndroid.show('Berhasil menambahkan data', ToastAndroid.SHORT) - router.push('/member?active=true') - } - }) - }} /> */} + setValSelect("position"); + setSelect(true); + }} + error={error.position} + errorText="Jabatan tidak boleh kosong" + /> + { + setValSelect("role"); + setSelect(true); + }} + error={error.role} + errorText="Role tidak boleh kosong" + /> + { + validationForm("nik", val) + }} + /> + { + validationForm("name", val) + }} + /> + { + validationForm("email", val) + }} + /> + +62} + error={error.phone} + errorText="Nomor Telepon tidak valid" + onChange={val => { + validationForm("phone", 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" ? chooseGroup.val : ""} + /> - ) -} \ No newline at end of file + ); +} diff --git a/app/(application)/member/index.tsx b/app/(application)/member/index.tsx index 2d42aae..108bcb8 100644 --- a/app/(application)/member/index.tsx +++ b/app/(application)/member/index.tsx @@ -1,13 +1,57 @@ import BorderBottomItem from "@/components/borderBottomItem"; import ButtonTab from "@/components/buttonTab"; +import ImageUser from "@/components/imageNew"; import InputSearch from "@/components/inputSearch"; import Styles from "@/constants/Styles"; +import { apiGetUser } from "@/lib/api"; +import { useAuthSession } from "@/providers/AuthProvider"; import { AntDesign, Feather } from "@expo/vector-icons"; import { router, useLocalSearchParams } from "expo-router"; -import { Image, SafeAreaView, ScrollView, Text, View } from "react-native"; +import { useEffect, useState } from "react"; +import { SafeAreaView, ScrollView, Text, View } from "react-native"; +import { useDispatch, 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 Index() { - const { active } = useLocalSearchParams<{ active?: string }>() + const { active, group } = useLocalSearchParams<{ active?: string, group?: string }>() + const { token, decryptToken } = useAuthSession() + const entityUser = useSelector((state: any) => state.user) + const [search, setSearch] = useState('') + const [nameGroup, setNameGroup] = useState('') + const [data, setData] = useState([]) + const dispatch = useDispatch() + const update = useSelector((state: any) => state.memberUpdate) + + async function handleLoad() { + try { + const hasil = await decryptToken(String(token?.current)) + const response = await apiGetUser({ user: hasil, active: String(active), search: search, group: String(group) }) + setData(response.data) + setNameGroup(response.filter.name) + } catch (error) { + console.error(error) + } + } + + useEffect(() => { + handleLoad() + }, [active, search, group, update]) + + + return ( @@ -17,100 +61,47 @@ export default function Index() { { router.push('/member?active=true') }} + onPress={() => { router.push(`/member?active=true&group=${group}&search=${search}`) }} label="Aktif" icon={} n={2} /> { router.push('/member?active=false') }} + onPress={() => { router.push(`/member?active=false&group=${group}&search=${search}`) }} label="Tidak Aktif" icon={} n={2} /> - - - Filter : Dinas - + + { + (entityUser.role == "supadmin" || entityUser.role == "developer") && + + Filter : {nameGroup} + + } - {router.push('/member/1')}} - borderType="all" - icon={ - - } - title="Amalia Dwi" - subtitle="Dinas - Sekretaris" - /> + { + data.length > 0 + ? + data.map((item, index) => { + return ( + { router.push(`/member/${item.id}`) }} + borderType="all" + icon={ + + } + title={item.name} + subtitle={`${item.group} - ${item.position}`} + /> + ) + }) + : + Tidak ada data - {router.push('/member/1')}} - borderType="all" - icon={ - - } - title="Amalia Dwi" - subtitle="Dinas - Sekretaris" - /> - - {router.push('/member/1')}} - borderType="all" - icon={ - - } - title="Amalia Dwi" - subtitle="Dinas - Sekretaris" - /> - - {router.push('/member/1')}} - borderType="all" - icon={ - - } - title="Amalia Dwi" - subtitle="Dinas - Sekretaris" - /> - - {router.push('/member/1')}} - borderType="all" - icon={ - - } - title="Amalia Dwi" - subtitle="Dinas - Sekretaris" - /> - - {router.push('/member/1')}} - borderType="all" - icon={ - - } - title="Amalia Dwi" - subtitle="Dinas - Sekretaris" - /> + } diff --git a/app/(application)/position/index.tsx b/app/(application)/position/index.tsx index 36e2867..0b3e587 100644 --- a/app/(application)/position/index.tsx +++ b/app/(application)/position/index.tsx @@ -115,14 +115,14 @@ export default function Index() { { router.push('/position?active=true') }} + onPress={() => { router.push(`/position?active=true&group=${group}&search=${search}`) }} label="Aktif" icon={} n={2} /> { router.push('/position?active=false') }} + onPress={() => { router.push(`/position?active=false&group=${group}&search=${search}`) }} label="Tidak Aktif" icon={} n={2} /> diff --git a/components/buttonSaveHeader.tsx b/components/buttonSaveHeader.tsx index 8728d26..14bfce0 100644 --- a/components/buttonSaveHeader.tsx +++ b/components/buttonSaveHeader.tsx @@ -1,6 +1,6 @@ import { Feather } from "@expo/vector-icons" -import { ButtonHeader } from "./buttonHeader" import AlertKonfirmasi from "./alertKonfirmasi" +import { ButtonHeader } from "./buttonHeader" type Props = { category: 'create' | 'update' | 'cancel' @@ -12,19 +12,21 @@ export default function ButtonSaveHeader({ category, onPress, disable }: Props) return ( <> } + item={} onPress={() => { - AlertKonfirmasi({ - title: 'Konfirmasi', - desc: category == 'create' - ? 'Apakah anda yakin ingin menambahkan data?' - : category == 'cancel' - ? 'Apakah anda yakin ingin membatalkan kegiatan? Pembatalan bersifat permanen' - : 'Apakah anda yakin mengubah data?', - onPress: () => { - onPress && onPress() - } - }) + if (!disable) { + AlertKonfirmasi({ + title: 'Konfirmasi', + desc: category == 'create' + ? 'Apakah anda yakin ingin menambahkan data?' + : category == 'cancel' + ? 'Apakah anda yakin ingin membatalkan kegiatan? Pembatalan bersifat permanen' + : 'Apakah anda yakin mengubah data?', + onPress: () => { + onPress && onPress() + } + }) + } } } diff --git a/components/imageNew.tsx b/components/imageNew.tsx new file mode 100644 index 0000000..0e4c61d --- /dev/null +++ b/components/imageNew.tsx @@ -0,0 +1,20 @@ +import Styles from "@/constants/Styles"; +import { useState } from "react"; +import { Image } from "react-native"; + +type Props = { + src: string +} + +export default function ImageUser({ src }: Props) { + const [error, setError] = useState(false) + return ( + + setError(true) + } + /> + ) +} \ No newline at end of file diff --git a/components/member/headerMemberList.tsx b/components/member/headerMemberList.tsx index 5e613a2..82a3778 100644 --- a/components/member/headerMemberList.tsx +++ b/components/member/headerMemberList.tsx @@ -3,6 +3,7 @@ import { AntDesign } from "@expo/vector-icons" import { router } from "expo-router" import { useState } from "react" import { View } from "react-native" +import { useSelector } from "react-redux" import ButtonMenuHeader from "../buttonMenuHeader" import DrawerBottom from "../drawerBottom" import MenuItemRow from "../menuItemRow" @@ -11,6 +12,8 @@ import ModalFilter from "../modalFilter" export default function HeaderMemberList() { const [isVisible, setVisible] = useState(false) const [isFilter, setFilter] = useState(false) + const entityUser = useSelector((state: any) => state.user) + return ( <> { setVisible(true) }} /> @@ -24,14 +27,17 @@ export default function HeaderMemberList() { router.push('/member/create') }} /> - } - title="Filter" - onPress={() => { - setVisible(false) - setFilter(true) - }} - /> + { + (entityUser.role == 'supadmin' || entityUser.role == 'developer') && + } + title="Filter" + onPress={() => { + setVisible(false) + setFilter(true) + }} + /> + } { setFilter(false) }} open={isFilter} page="member" /> diff --git a/components/modalSelect.tsx b/components/modalSelect.tsx index f754720..c372698 100644 --- a/components/modalSelect.tsx +++ b/components/modalSelect.tsx @@ -1,5 +1,7 @@ +import { valueGender } from "@/constants/Gender" +import { valueRoleUser } from "@/constants/RoleUser" import Styles from "@/constants/Styles" -import { apiGetGroup } from "@/lib/api" +import { apiGetGroup, apiGetPosition } from "@/lib/api" import { setEntityFilterGroup } from "@/lib/filterSlice" import { useAuthSession } from "@/providers/AuthProvider" import { AntDesign } from "@expo/vector-icons" @@ -12,17 +14,18 @@ type Props = { open: boolean close: (value: boolean) => void title: string - category: 'group' | 'status-task' - + category: 'group' | 'status-task' | 'position' | 'role' | 'gender' + idParent?: string onSelect: (value: { val: string, label: string }) => void } -export default function ModalSelect({ open, close, title, category, onSelect }: Props) { - // const [isChoose, setChoose] = useState(choose) +export default function ModalSelect({ open, close, title, category, idParent, onSelect }: 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 [data, setData] = useState([]) async function handleLoadGroup() { const hasil = await decryptToken(String(token?.current)) @@ -30,11 +33,39 @@ export default function ModalSelect({ open, close, title, category, onSelect }: dispatch(setEntityFilterGroup(response.data)) } - useEffect(() => { - if (entitiesGroup.length == 0 && category == 'group') { - handleLoadGroup() + async function handleLoadPosition() { + const hasil = await decryptToken(String(token?.current)) + if (idParent == undefined || idParent == '' || idParent == null) { + setData([]) + } else { + const response = await apiGetPosition({ active: 'true', user: hasil, search: '', group: idParent }) + setData(response.data) } - }, [dispatch]); + } + + function handleLoadUserRole() { + const filter = valueRoleUser.filter((v) => v.login == entityUser.role)[0]?.data + setData(filter) + } + + function handleLoadGender() { + setData(valueGender) + } + + + useEffect(() => { + if (category == 'group') { + if (entitiesGroup.length == 0) + handleLoadGroup() + setData(entitiesGroup) + } else if (category == 'position') { + handleLoadPosition() + } else if (category == "role") { + handleLoadUserRole() + } else if (category == "gender") { + handleLoadGender() + } + }, [dispatch, open]); function onChoose(val: string, label: string) { setChooseValue({ val, label }) @@ -43,19 +74,22 @@ export default function ModalSelect({ open, close, title, category, onSelect }: } return ( - + { - category == 'group' ? - entitiesGroup.map((item: any, index: any) => ( - { onChoose(item.id, item.name) }}> - {item.name} - { - chooseValue.val == item.id && - } - - )) + category != 'status-task' ? + data.length > 0 ? + data.map((item: any, index: any) => ( + { onChoose(item.id, item.name) }}> + {item.name} + { + chooseValue.val == item.id && + } + + )) + : + Tidak ada data : <> { diff --git a/constants/Gender.ts b/constants/Gender.ts new file mode 100644 index 0000000..211b9ee --- /dev/null +++ b/constants/Gender.ts @@ -0,0 +1,14 @@ +export const valueGender = + [ + { + id: "M", + name: "Laki-Laki" + }, + { + id: "F", + name: "Perempuan" + } + ] + + + diff --git a/constants/RoleUser.ts b/constants/RoleUser.ts new file mode 100644 index 0000000..ad27bdc --- /dev/null +++ b/constants/RoleUser.ts @@ -0,0 +1,91 @@ +export const valueRoleUser = + [ + { + login: "developer", + data: [ + { + id: "supadmin", + name: "Super Admin" + }, + { + id: "cosupadmin", + name: "Wakil Super Admin" + }, + { + id: "admin", + name: "Admin" + }, + { + id: "coadmin", + name: "Wakil Admin" + }, + { + id: "user", + name: "User" + }, + ] + }, + { + login: "supadmin", + data: [ + { + id: "cosupadmin", + name: "Wakil Super Admin" + }, + { + id: "admin", + name: "Admin" + }, + { + id: "coadmin", + name: "Wakil Admin" + }, + { + id: "user", + name: "User" + }, + ] + }, + { + login: "cosupadmin", + data: [ + { + id: "admin", + name: "Admin" + }, + { + id: "coadmin", + name: "Wakil Admin" + }, + { + id: "user", + name: "User" + }, + ], + }, + { + login: "admin", + data: [ + { + id: "coadmin", + name: "Wakil Admin" + }, + { + id: "user", + name: "User" + }, + ], + }, + { + login: "coadmin", + data: [ + { + id: "user", + name: "User" + }, + ], + } + ] + + + diff --git a/lib/api.ts b/lib/api.ts index 65feb60..bd62116 100644 --- a/lib/api.ts +++ b/lib/api.ts @@ -112,4 +112,19 @@ export const apiEditPosition = async (data: { user: string, name: string, idGrou .catch(error => { console.error('Error:', error); }); +}; + +export const apiGetUser = async ({ user, active, search, group }: { user: string, active: string, search: string, group?: string }) => { + const response = await api.get(`mobile/user?user=${user}&active=${active}&group=${group}&search=${search}`); + return response.data; +}; + + +export const apiCreateUser = async (data: FormData) => { + const response = await api.post('/mobile/user', data, { + headers: { + 'Content-Type': 'multipart/form-data', + }, + }) + return response.data; }; \ No newline at end of file diff --git a/lib/memberSlice.ts b/lib/memberSlice.ts new file mode 100644 index 0000000..5cdbd9e --- /dev/null +++ b/lib/memberSlice.ts @@ -0,0 +1,14 @@ +import { createSlice } from '@reduxjs/toolkit'; + +const memberUpdate = createSlice({ + name: 'memberUpdate', + initialState: false, + reducers: { + setUpdateMember: (state, action) => { + return action.payload; + }, + }, +}); + +export const { setUpdateMember } = memberUpdate.actions; +export default memberUpdate.reducer; \ No newline at end of file diff --git a/lib/store.ts b/lib/store.ts index 9138056..2a75b74 100644 --- a/lib/store.ts +++ b/lib/store.ts @@ -3,6 +3,7 @@ import bannerReducer from './bannerSlice'; import entitiesReducer from './entitiesSlice'; import filterSlice from './filterSlice'; import groupUpdate from './groupSlice'; +import memberUpdate from './memberSlice'; import positionUpdate from './positionSlice'; import userReducer from './userSlice'; @@ -14,6 +15,7 @@ const store = configureStore({ user: userReducer, groupUpdate: groupUpdate, positionUpdate: positionUpdate, + memberUpdate: memberUpdate, filterGroup: filterSlice, } });