diff --git a/app/(application)/division/[id]/(fitur-division)/calendar/[detail]/index.tsx b/app/(application)/division/[id]/(fitur-division)/calendar/[detail]/index.tsx index 39f4029..f9f81fb 100644 --- a/app/(application)/division/[id]/(fitur-division)/calendar/[detail]/index.tsx +++ b/app/(application)/division/[id]/(fitur-division)/calendar/[detail]/index.tsx @@ -13,9 +13,10 @@ import { apiDeleteCalendarMember, apiGetCalendarOne, apiGetDivisionOneFeature } import { setUpdateCalendar } from "@/lib/calendarUpdate" import { useAuthSession } from "@/providers/AuthProvider" import { MaterialCommunityIcons } from "@expo/vector-icons" +import Clipboard from "@react-native-clipboard/clipboard" import { router, Stack, useLocalSearchParams } from "expo-router" import { useEffect, useState } from "react" -import { RefreshControl, SafeAreaView, ScrollView, View } from "react-native" +import { Pressable, RefreshControl, SafeAreaView, ScrollView, View } from "react-native" import Toast from "react-native-toast-message" import { useDispatch, useSelector } from "react-redux" @@ -115,6 +116,11 @@ export default function DetailEventCalendar() { handleLoadMember(); }, [update.member]); + const handleCopy = (text: string) => { + Clipboard.setString(text); + Toast.show({ type: 'small', text1: 'Berhasil menyalin link', }) + }; + async function handleDeleteUser() { try { const hasil = await decryptToken(String(token?.current)); @@ -216,10 +222,14 @@ export default function DetailEventCalendar() { loading ? : - {data?.linkMeet ? data.linkMeet : '-'} + data?.linkMeet ? + { handleCopy(data.linkMeet) }}> + {data.linkMeet} + + : - } - + { loading ? diff --git a/app/(application)/division/[id]/(fitur-division)/document/index.tsx b/app/(application)/division/[id]/(fitur-division)/document/index.tsx index 3668857..3f90895 100644 --- a/app/(application)/division/[id]/(fitur-division)/document/index.tsx +++ b/app/(application)/division/[id]/(fitur-division)/document/index.tsx @@ -65,6 +65,7 @@ type PropsPath = { }; export default function DocumentDivision() { + const [loadingRename, setLoadingRename] = useState(false) const [isShare, setShare] = useState(false) const { token, decryptToken } = useAuthSession() const { id } = useLocalSearchParams<{ id: string }>() @@ -201,6 +202,7 @@ export default function DocumentDivision() { async function handleRename() { try { + setLoadingRename(true) const hasil = await decryptToken(String(token?.current)); const response = await apiDocumentRename({ user: hasil, ...bodyRename }); if (response.success) { @@ -214,7 +216,8 @@ export default function DocumentDivision() { console.error(error); Toast.show({ type: 'small', text1: 'Terjadi kesalahan', }) } finally { - setRename(false); + setLoadingRename(false) + setRename(false) } } @@ -360,25 +363,20 @@ export default function DocumentDivision() { { - loading ? - arrSkeleton.map((item, index) => ( - - )) - : - dataJalur.map((item, index) => ( - { - setPath(item.id); - }} - > - {item.id != "home" && ( - - )} - {item.name} - - )) + dataJalur.map((item, index) => ( + { + setPath(item.id); + }} + > + {item.id != "home" && ( + + )} + {item.name} + + )) } @@ -538,7 +536,7 @@ export default function DocumentDivision() { isVisible={isRename} setVisible={() => { setRename(false) }} onSubmit={() => { handleRename() }} - disableSubmit={bodyRename.name == ""} + disableSubmit={bodyRename.name == "" || loadingRename} > state.dokumenUpdate) const [loading, setLoading] = useState(false) + const [loadingFolder, setLoadingFolder] = useState(false) async function handleCreateFolder() { try { + setLoadingFolder(true) const hasil = await decryptToken(String(token?.current)) const response = await apiCreateFolderDocument({ data: { user: hasil, name, path, idDivision: id } }) if (response.success) { @@ -39,6 +41,7 @@ export default function HeaderRightDocument({ path }: { path: string }) { console.error(error) Toast.show({ type: 'small', text1: 'Terjadi kesalahan', }) } finally { + setLoadingFolder(false) setNewFolder(false) } } @@ -148,7 +151,7 @@ export default function HeaderRightDocument({ path }: { path: string }) { title="Buat Folder Baru" isVisible={newFolder} setVisible={() => { setNewFolder(false) }} - disableSubmit={name == ""} + disableSubmit={name == "" || loadingFolder} onSubmit={() => { handleCreateFolder() }} > diff --git a/components/document/modalNewFolder.tsx b/components/document/modalNewFolder.tsx new file mode 100644 index 0000000..43927ac --- /dev/null +++ b/components/document/modalNewFolder.tsx @@ -0,0 +1,65 @@ +import Styles from "@/constants/Styles"; +import { apiCreateFolderDocument } from "@/lib/api"; +import { useAuthSession } from "@/providers/AuthProvider"; +import { useLocalSearchParams } from "expo-router"; +import { useState } from "react"; +import { Pressable, View } from "react-native"; +import Toast from "react-native-toast-message"; +import Text from "../Text"; +import { InputForm } from "../inputForm"; +import ModalFloat from "../modalFloat"; + +export function ModalNewFolder({ path, onCreated }: { path: string, onCreated: () => void }) { + const { token, decryptToken } = useAuthSession() + const [newFolder, setNewFolder] = useState(false) + const [name, setName] = useState("") + const [loadingFolder, setLoadingFolder] = useState(false) + const { id } = useLocalSearchParams<{ id: string }>(); + + + async function handleCreateFolder() { + try { + setLoadingFolder(true) + const hasil = await decryptToken(String(token?.current)) + const response = await apiCreateFolderDocument({ data: { user: hasil, name, path, idDivision: id } }) + if (response.success) { + Toast.show({ type: 'small', text1: 'Berhasil membuat folder baru', }) + } else { + Toast.show({ type: 'small', text1: response.message, }) + } + } catch (error) { + console.error(error) + Toast.show({ type: 'small', text1: 'Terjadi kesalahan', }) + } finally { + onCreated() + setLoadingFolder(false) + setNewFolder(false) + } + } + + return ( + <> + setNewFolder(true)}> + FOLDER BARU + + + { setNewFolder(false) }} + disableSubmit={name == "" || loadingFolder} + onSubmit={() => { handleCreateFolder() }} + > + + { setName(value) }} + /> + + + + ) +} \ No newline at end of file diff --git a/components/document/modalSalinMove.tsx b/components/document/modalSalinMove.tsx index 670eb8e..01a4e96 100644 --- a/components/document/modalSalinMove.tsx +++ b/components/document/modalSalinMove.tsx @@ -8,6 +8,7 @@ import { Pressable, View } from "react-native" import BorderBottomItem from "../borderBottomItem" import DrawerBottom from "../drawerBottom" import Text from "../Text" +import { ModalNewFolder } from "./modalNewFolder" type Props = { open: boolean @@ -106,9 +107,7 @@ export default function ModalSalinMove({ open, close, category, onConfirm, dataC } - close(false)}> - BATAL - + getData()} /> onConfirm(path)}> {category == 'copy' ? 'SALIN' : 'PINDAH'} diff --git a/components/imageWithLabel.tsx b/components/imageWithLabel.tsx index dddfcfe..0bda9b4 100644 --- a/components/imageWithLabel.tsx +++ b/components/imageWithLabel.tsx @@ -11,7 +11,7 @@ type Props = { export default function ImageWithLabel({ src, label, onClick }: Props) { return ( - + {label} diff --git a/components/inputDate.tsx b/components/inputDate.tsx index 40f7710..f627855 100644 --- a/components/inputDate.tsx +++ b/components/inputDate.tsx @@ -28,7 +28,7 @@ type Props = { export function InputDate({ label, value, placeholder, onChange, info, disable, error, errorText, required, mode, round, width, }: Props) { const [modal, setModal] = useState(false); const [valueFix, setValueFix] = useState(new Date()) - const [valueFirst, setValueFirst] = useState("") + const [valueFirst, setValueFirst] = useState(mode == "date" ? dayjs(new Date()).format("DD-MM-YYYY") : mode == "time" ? dayjs(new Date()).format("HH:mm") : "") const onChangeDate = (type: string, selectedDate: any) => { if (type === "set") { @@ -45,6 +45,8 @@ export function InputDate({ label, value, placeholder, onChange, info, disable, onChange(formatted) setModal(false) } + } else if (type === "dismissed") { + setModal(false) } }; @@ -100,11 +102,8 @@ export function InputDate({ label, value, placeholder, onChange, info, disable, value={valueFix} mode={mode} display="spinner" - onChange={(event, date) => { - onChangeDate(event.type, date) - }} + onChange={(event, date) => { onChangeDate(event.type, date) }} onTouchCancel={() => setModal(false)} - /> ) @@ -115,7 +114,7 @@ export function InputDate({ label, value, placeholder, onChange, info, disable, mode={mode} display="inline" onChange={(event, date) => { onChangeDate(event.type, date) }} - onTouchCancel={() => setModal(false)} + onTouchCancel={() => { setModal(false) }} /> ) ) diff --git a/components/position/headerRightPositionList.tsx b/components/position/headerRightPositionList.tsx index 2e20af1..549623f 100644 --- a/components/position/headerRightPositionList.tsx +++ b/components/position/headerRightPositionList.tsx @@ -1,93 +1,19 @@ import Styles from "@/constants/Styles" -import { apiCreatePosition } from "@/lib/api" -import { setUpdatePosition } from "@/lib/positionSlice" -import { useAuthSession } from "@/providers/AuthProvider" import { AntDesign } from "@expo/vector-icons" -import { useEffect, useState } from "react" +import { useState } from "react" import { View } from "react-native" -import Toast from "react-native-toast-message" -import { useDispatch, useSelector } from "react-redux" -import { ButtonForm } from "../buttonForm" +import { useSelector } from "react-redux" import ButtonMenuHeader from "../buttonMenuHeader" import DrawerBottom from "../drawerBottom" -import { InputForm } from "../inputForm" import MenuItemRow from "../menuItemRow" import ModalFilter from "../modalFilter" -import ModalSelect from "../modalSelect" -import SelectForm from "../selectForm" +import ModalFormCreatePosition from "./modalFormCreatePosition" export default function HeaderRightPositionList() { - const dispatch = useDispatch() - const update = useSelector((state: any) => state.positionUpdate) - const { token, decryptToken } = useAuthSession() const entityUser = useSelector((state: any) => state.user) const [isVisible, setVisible] = useState(false) const [isVisibleTambah, setVisibleTambah] = useState(false) const [isFilter, setFilter] = useState(false) - const [isSelect, setSelect] = useState(false) - const [choose, setChoose] = useState({ val: '', label: '' }) - const [disable, setDisable] = useState(true) - const [dataForm, setDataForm] = useState({ - name: "", - idGroup: "", - }) - const [error, setError] = useState({ - name: false, - idGroup: false - }); - - function validationForm(val: any, cat: 'name' | 'idGroup') { - if (cat === 'name') { - setDataForm({ ...dataForm, name: val }) - if (val == "") { - setError({ ...error, name: true }) - } else { - setError({ ...error, name: false }) - } - } else if (cat === "idGroup") { - setDataForm({ ...dataForm, idGroup: val }) - if (val == "") { - setError({ ...error, idGroup: true }) - } else { - setError({ ...error, idGroup: false }) - } - } - } - - function checkAll() { - let nilai = false - if (dataForm.name == "") { - nilai = true - } - - if ((entityUser.role == "supadmin" || entityUser.role == "developer") && (dataForm.idGroup == "" || String(dataForm.idGroup) == "null")) { - nilai = true - } - - setDisable(nilai) - } - - useEffect(() => { - checkAll() - }, [dataForm]) - - async function handleTambah() { - try { - setDisable(true) - const hasil = await decryptToken(String(token?.current)) - const response = await apiCreatePosition({ user: hasil, name: dataForm.name, idGroup: dataForm.idGroup }) - dispatch(setUpdatePosition(!update)) - } catch (error) { - console.error(error) - } finally { - setDisable(false) - setVisibleTambah(false) - setVisible(false) - Toast.show({ type: 'small', text1: 'Berhasil menambahkan data', }) - } - - } - return ( <> @@ -121,64 +47,13 @@ export default function HeaderRightPositionList() { setVisibleTambah(false)} title="Tambah Jabatan"> - - - { - (entityUser.role == 'supadmin' || entityUser.role == 'developer') && - { - setVisibleTambah(false) - setTimeout(() => { - setSelect(true) - }, 600) - }} - error={error.idGroup} - errorText="Lembaga Desa harus diisi" - /> - } - { validationForm(value, 'name') }} - error={error.name} - errorText="Nama jabatan harus diisi" - value={dataForm.name} - /> - - - { handleTambah() }} - disabled={disable} /> - - + setVisibleTambah(false)} /> { setFilter(false) setVisible(false) }} open={isFilter} page="position" /> - - { - validationForm(value.val, 'idGroup') - setChoose(value) - setSelect(false) - setTimeout(() => { - setVisibleTambah(true) - }, 600) - }} - title="Lembaga Desa" - open={isSelect} - /> ) } \ No newline at end of file diff --git a/components/position/modalFormCreatePosition.tsx b/components/position/modalFormCreatePosition.tsx new file mode 100644 index 0000000..256a3f3 --- /dev/null +++ b/components/position/modalFormCreatePosition.tsx @@ -0,0 +1,133 @@ +import Styles from "@/constants/Styles" +import { apiCreatePosition } from "@/lib/api" +import { setUpdatePosition } from "@/lib/positionSlice" +import { useAuthSession } from "@/providers/AuthProvider" +import { update } from "@react-native-firebase/database" +import { useEffect, useState } from "react" +import { View } from "react-native" +import Toast from "react-native-toast-message" +import { useDispatch, useSelector } from "react-redux" +import { ButtonForm } from "../buttonForm" +import { InputForm } from "../inputForm" +import SelectForm from "../selectForm" +import ModalSelect from "../modalSelect" + +export default function ModalFormCreatePosition({ onClose }: { onClose: () => void }) { + const dispatch = useDispatch() + const { token, decryptToken } = useAuthSession() + const entityUser = useSelector((state: any) => state.user) + const [choose, setChoose] = useState({ val: '', label: '' }) + const [isSelect, setSelect] = useState(false) + const [disable, setDisable] = useState(true) + const [dataForm, setDataForm] = useState({ + name: "", + idGroup: "", + }) + const [error, setError] = useState({ + name: false, + idGroup: false + }); + + function validationForm(val: any, cat: 'name' | 'idGroup') { + if (cat === 'name') { + setDataForm({ ...dataForm, name: val }) + if (val == "") { + setError({ ...error, name: true }) + } else { + setError({ ...error, name: false }) + } + } else if (cat === "idGroup") { + setDataForm({ ...dataForm, idGroup: val }) + if (val == "") { + setError({ ...error, idGroup: true }) + } else { + setError({ ...error, idGroup: false }) + } + } + } + + function checkAll() { + let nilai = false + if (dataForm.name == "") { + nilai = true + } + + if ((entityUser.role == "supadmin" || entityUser.role == "developer") && (dataForm.idGroup == "" || String(dataForm.idGroup) == "null")) { + nilai = true + } + + setDisable(nilai) + } + + useEffect(() => { + checkAll() + }, [dataForm]) + + async function handleTambah() { + try { + setDisable(true) + const hasil = await decryptToken(String(token?.current)) + const response = await apiCreatePosition({ user: hasil, name: dataForm.name, idGroup: dataForm.idGroup }) + dispatch(setUpdatePosition(!update)) + } catch (error) { + console.error(error) + } finally { + setDisable(false) + Toast.show({ type: 'small', text1: 'Berhasil menambahkan data', }) + onClose() + } + + } + + return ( + <> + + + { + (entityUser.role == 'supadmin' || entityUser.role == 'developer') && + { + setSelect(true) + }} + error={error.idGroup} + errorText="Lembaga Desa harus diisi" + /> + } + { validationForm(value, 'name') }} + error={error.name} + errorText="Nama jabatan harus diisi" + value={dataForm.name} + /> + + + { handleTambah() }} + disabled={disable} /> + + + + { + validationForm(value.val, 'idGroup') + setChoose(value) + setSelect(false) + }} + title="Lembaga Desa" + open={isSelect} + valChoose={choose.val} + /> + + ) +} \ No newline at end of file diff --git a/constants/Styles.ts b/constants/Styles.ts index b76b1d3..726a5f1 100644 --- a/constants/Styles.ts +++ b/constants/Styles.ts @@ -94,6 +94,9 @@ const Styles = StyleSheet.create({ mv15: { marginVertical: 15 }, + mh03: { + marginHorizontal: 3 + }, mh05: { marginHorizontal: 5 }, diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 3e509e4..a7e1e3f 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -2027,6 +2027,8 @@ PODS: - React-utils (= 0.79.5) - RNCAsyncStorage (2.1.2): - React-Core + - RNCClipboard (1.16.3): + - React-Core - RNDateTimePicker (8.4.1): - React-Core - RNFBApp (22.4.0): @@ -2328,6 +2330,7 @@ DEPENDENCIES: - ReactCodegen (from `build/generated/ios`) - ReactCommon/turbomodule/core (from `../node_modules/react-native/ReactCommon`) - "RNCAsyncStorage (from `../node_modules/@react-native-async-storage/async-storage`)" + - "RNCClipboard (from `../node_modules/@react-native-clipboard/clipboard`)" - "RNDateTimePicker (from `../node_modules/@react-native-community/datetimepicker`)" - "RNFBApp (from `../node_modules/@react-native-firebase/app`)" - "RNFBDatabase (from `../node_modules/@react-native-firebase/database`)" @@ -2570,6 +2573,8 @@ EXTERNAL SOURCES: :path: "../node_modules/react-native/ReactCommon" RNCAsyncStorage: :path: "../node_modules/@react-native-async-storage/async-storage" + RNCClipboard: + :path: "../node_modules/@react-native-clipboard/clipboard" RNDateTimePicker: :path: "../node_modules/@react-native-community/datetimepicker" RNFBApp: @@ -2712,6 +2717,7 @@ SPEC CHECKSUMS: ReactCodegen: 272c9bc1a8a917bf557bd9d032a4b3e181c6abfe ReactCommon: 7eb76fcd5133313d8c6a138a5c7dd89f80f189d5 RNCAsyncStorage: b9f5f78da5d16a853fe3dc22e8268d932fc45a83 + RNCClipboard: f6679d470d0da2bce2a37b0af7b9e0bf369ecda5 RNDateTimePicker: 60f9e986d61e42169a2716c1b51f1f93dfa82665 RNFBApp: 12884d3bf9b3a0223efe4a0adce516edf72c4102 RNFBDatabase: 1e5c4bda4bb47a48820089ddef498f9af21cb52b diff --git a/package.json b/package.json index 815c340..31b5d7b 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "@expo/vector-icons": "^14.0.2", "@formatjs/intl-getcanonicallocales": "^2.5.5", "@react-native-async-storage/async-storage": "2.1.2", + "@react-native-clipboard/clipboard": "^1.16.3", "@react-native-community/cli": "^19.1.0", "@react-native-community/datetimepicker": "8.4.1", "@react-native-firebase/app": "^22.4.0",