diff --git a/app/(application)/division/[id]/(fitur-division)/task/[detail]/index.tsx b/app/(application)/division/[id]/(fitur-division)/task/[detail]/index.tsx index 68720da..344b118 100644 --- a/app/(application)/division/[id]/(fitur-division)/task/[detail]/index.tsx +++ b/app/(application)/division/[id]/(fitur-division)/task/[detail]/index.tsx @@ -3,6 +3,7 @@ import SectionCancel from "@/components/sectionCancel"; import SectionProgress from "@/components/sectionProgress"; import HeaderRightTaskDetail from "@/components/task/headerTaskDetail"; import SectionFileTask from "@/components/task/sectionFileTask"; +import SectionLinkTask from "@/components/task/sectionLinkTask"; import SectionMemberTask from "@/components/task/sectionMemberTask"; import SectionTanggalTugasTask from "@/components/task/sectionTanggalTugasTask"; import Styles from "@/constants/Styles"; @@ -90,6 +91,7 @@ export default function DetailTaskDivision() { + diff --git a/app/(application)/project/[id]/index.tsx b/app/(application)/project/[id]/index.tsx index 1b5e1a5..258f2fb 100644 --- a/app/(application)/project/[id]/index.tsx +++ b/app/(application)/project/[id]/index.tsx @@ -1,6 +1,7 @@ import ButtonBackHeader from "@/components/buttonBackHeader"; import HeaderRightProjectDetail from "@/components/project/headerProjectDetail"; import SectionFile from "@/components/project/sectionFile"; +import SectionLink from "@/components/project/sectionLink"; import SectionMember from "@/components/project/sectionMember"; import SectionTanggalTugasProject from "@/components/project/sectionTanggalTugas"; import SectionCancel from "@/components/sectionCancel"; @@ -111,8 +112,9 @@ export default function DetailProject() { data?.reason != null && data?.reason != "" && } - + + diff --git a/components/project/headerProjectDetail.tsx b/components/project/headerProjectDetail.tsx index c7b53d3..57b0c7a 100644 --- a/components/project/headerProjectDetail.tsx +++ b/components/project/headerProjectDetail.tsx @@ -1,8 +1,8 @@ import Styles from "@/constants/Styles" -import { apiDeleteProject } from "@/lib/api" +import { apiAddLinkProject, apiDeleteProject } from "@/lib/api" import { setUpdateProject } from "@/lib/projectUpdate" import { useAuthSession } from "@/providers/AuthProvider" -import { AntDesign, Ionicons, MaterialCommunityIcons, MaterialIcons } from "@expo/vector-icons" +import { AntDesign, Feather, Ionicons, MaterialCommunityIcons, MaterialIcons } from "@expo/vector-icons" import { router } from "expo-router" import { useState } from "react" import { View } from "react-native" @@ -11,7 +11,9 @@ import { useDispatch, useSelector } from "react-redux" import AlertKonfirmasi from "../alertKonfirmasi" import ButtonMenuHeader from "../buttonMenuHeader" import DrawerBottom from "../drawerBottom" +import { InputForm } from "../inputForm" import MenuItemRow from "../menuItemRow" +import ModalFloat from "../modalFloat" type Props = { id: string | string[] @@ -24,6 +26,8 @@ export default function HeaderRightProjectDetail({ id, status }: Props) { const [isVisible, setVisible] = useState(false) const dispatch = useDispatch() const update = useSelector((state: any) => state.projectUpdate) + const [isAddLink, setAddLink] = useState(false) + const [link, setLink] = useState("") async function handleDelete() { try { @@ -43,6 +47,23 @@ export default function HeaderRightProjectDetail({ id, status }: Props) { } } + async function handleAddLink() { + try { + const hasil = await decryptToken(String(token?.current)) + const response = await apiAddLinkProject({ user: hasil, link }, String(id)) + if (response.success) { + dispatch(setUpdateProject({ ...update, link: !update.link })) + Toast.show({ type: 'small', text1: 'Berhasil menambahkan link', }) + } else { + Toast.show({ type: 'small', text1: 'Gagal menambahkan link', }) + } + } catch (error) { + console.error(error) + } finally { + setAddLink(false) + } + } + return ( <> { setVisible(true) }} /> @@ -68,8 +89,22 @@ export default function HeaderRightProjectDetail({ id, status }: Props) { }} disabled={status == 3} /> - { - entityUser.role != "user" && entityUser.role != "coadmin" && + } + title="Tambah Link" + onPress={() => { + if (status == 3) return + setVisible(false) + setTimeout(() => { + setAddLink(true) + }, 600) + }} + disabled={status == 3} + /> + + { + entityUser.role != "user" && entityUser.role != "coadmin" && + } title="Tambah Anggota" @@ -80,11 +115,6 @@ export default function HeaderRightProjectDetail({ id, status }: Props) { }} disabled={status == 3} /> - } - - { - entityUser.role != "user" && entityUser.role != "coadmin" && - } title="Edit" @@ -123,6 +153,23 @@ export default function HeaderRightProjectDetail({ id, status }: Props) { } + + { setAddLink(false) }} + onSubmit={() => { handleAddLink() }} + disableSubmit={link == ""} + > + + { setLink(text) }} + /> + + ) } \ No newline at end of file diff --git a/components/project/sectionLink.tsx b/components/project/sectionLink.tsx new file mode 100644 index 0000000..cd81e4b --- /dev/null +++ b/components/project/sectionLink.tsx @@ -0,0 +1,140 @@ +import Styles from "@/constants/Styles"; +import { apiDeleteLinkProject, apiGetProjectOne } from "@/lib/api"; +import { urlCompleted } from "@/lib/fun_urlCompleted"; +import { setUpdateProject } from "@/lib/projectUpdate"; +import { useAuthSession } from "@/providers/AuthProvider"; +import { Feather, Ionicons } from "@expo/vector-icons"; +import { useLocalSearchParams } from "expo-router"; +import { useEffect, useState } from "react"; +import { Linking, View } from "react-native"; +import Toast from "react-native-toast-message"; +import { useDispatch, useSelector } from "react-redux"; +import AlertKonfirmasi from "../alertKonfirmasi"; +import BorderBottomItem from "../borderBottomItem"; +import DrawerBottom from "../drawerBottom"; +import MenuItemRow from "../menuItemRow"; +import Text from "../Text"; + + +type Props = { + id: string + link: string +} + +export default function SectionLink({ status, member, refreshing }: { status: number | undefined, member: boolean, refreshing?: boolean }) { + const entityUser = useSelector((state: any) => state.user) + const [isModal, setModal] = useState(false) + const { token, decryptToken } = useAuthSession(); + const { id } = useLocalSearchParams<{ id: string }>(); + const [data, setData] = useState([]); + const update = useSelector((state: any) => state.projectUpdate) + const dispatch = useDispatch() + const [selectLink, setSelectLink] = useState(null) + + async function handleLoad() { + try { + const hasil = await decryptToken(String(token?.current)); + const response = await apiGetProjectOne({ + user: hasil, + cat: "link", + id: id, + }); + setData(response.data); + } catch (error) { + console.error(error); + } + } + + useEffect(() => { + handleLoad(); + }, [update.link]); + + useEffect(() => { + if (refreshing) + handleLoad(); + }, [refreshing]); + + + async function handleDelete() { + try { + const hasil = await decryptToken(String(token?.current)); + const response = await apiDeleteLinkProject({ user: hasil, idLink: String(selectLink?.id) }, String(id)); + if (response.success) { + Toast.show({ type: 'small', text1: 'Berhasil menghapus link', }) + dispatch(setUpdateProject({ ...update, link: !update.link })) + } else { + Toast.show({ type: 'small', text1: response.message, }) + } + } catch (error) { + console.error(error); + Toast.show({ type: 'small', text1: 'Terjadi kesalahan', }) + } finally { + setModal(false) + } + } + + + + return ( + <> + { + data.length > 0 && + <> + + Link + + { + data.map((item, index) => { + return ( + } + title={item.link} + titleWeight="normal" + onPress={() => { setSelectLink(item); setModal(true) }} + width={65} + /> + ) + }) + } + + + + + + } + title="Buka Link" + onPress={() => { + Linking.openURL(urlCompleted(String(selectLink?.link))) + }} + /> + { + !member && (entityUser.role == "user" || entityUser.role == "coadmin") ? <> + : + } + title="Hapus" + disabled={status == 3} + onPress={() => { + if (status == 3) return + setModal(false) + AlertKonfirmasi({ + title: 'Konfirmasi', + desc: 'Apakah Anda yakin ingin menghapus link ini? Link yang dihapus tidak dapat dikembalikan', + onPress: () => { + handleDelete() + } + }) + + }} + /> + } + + + + } + + ) +} \ No newline at end of file diff --git a/components/task/headerTaskDetail.tsx b/components/task/headerTaskDetail.tsx index 3163946..816738f 100644 --- a/components/task/headerTaskDetail.tsx +++ b/components/task/headerTaskDetail.tsx @@ -1,8 +1,8 @@ import Styles from "@/constants/Styles" -import { apiDeleteTask, apiGetDivisionOneFeature } from "@/lib/api" +import { apiAddLinkTask, apiDeleteTask, apiGetDivisionOneFeature } from "@/lib/api" import { setUpdateTask } from "@/lib/taskUpdate" import { useAuthSession } from "@/providers/AuthProvider" -import { AntDesign, Ionicons, MaterialCommunityIcons, MaterialIcons } from "@expo/vector-icons" +import { AntDesign, Feather, Ionicons, MaterialCommunityIcons, MaterialIcons } from "@expo/vector-icons" import { router } from "expo-router" import { useEffect, useState } from "react" import { View } from "react-native" @@ -11,7 +11,9 @@ import { useDispatch, useSelector } from "react-redux" import AlertKonfirmasi from "../alertKonfirmasi" import ButtonMenuHeader from "../buttonMenuHeader" import DrawerBottom from "../drawerBottom" +import { InputForm } from "../inputForm" import MenuItemRow from "../menuItemRow" +import ModalFloat from "../modalFloat" type Props = { id: string | string[] @@ -27,6 +29,8 @@ export default function HeaderRightTaskDetail({ id, division, status }: Props) { const [isAdminDivision, setIsAdminDivision] = useState(false); const dispatch = useDispatch() const update = useSelector((state: any) => state.taskUpdate) + const [isAddLink, setAddLink] = useState(false) + const [link, setLink] = useState("") async function handleCheckMember() { try { @@ -72,6 +76,23 @@ export default function HeaderRightTaskDetail({ id, division, status }: Props) { } } + async function handleAddLink() { + try { + const hasil = await decryptToken(String(token?.current)) + const response = await apiAddLinkTask({ user: hasil, link, idDivision: division }, String(id)) + if (response.success) { + dispatch(setUpdateTask({ ...update, link: !update.link })) + Toast.show({ type: 'small', text1: 'Berhasil menambahkan link', }) + } else { + Toast.show({ type: 'small', text1: 'Gagal menambahkan link', }) + } + } catch (error) { + console.error(error) + } finally { + setAddLink(false) + } + } + return ( <> { @@ -102,9 +123,25 @@ export default function HeaderRightTaskDetail({ id, division, status }: Props) { }} disabled={status == 3} /> - { - ((entityUser.role != "user" && entityUser.role != "coadmin") || isAdminDivision) - && + + } + title="Tambah Link" + onPress={() => { + if (status == 3) return + setVisible(false) + setTimeout(() => { + setAddLink(true) + }, 600) + }} + disabled={status == 3} + /> + + + { + ((entityUser.role != "user" && entityUser.role != "coadmin") || isAdminDivision) + && + } title="Tambah Anggota" @@ -115,15 +152,6 @@ export default function HeaderRightTaskDetail({ id, division, status }: Props) { }} disabled={status == 3} /> - - } - - - - { - ((entityUser.role != "user" && entityUser.role != "coadmin") || isAdminDivision) - && - } title="Edit" @@ -163,6 +191,23 @@ export default function HeaderRightTaskDetail({ id, division, status }: Props) { } + + { setAddLink(false) }} + onSubmit={() => { handleAddLink() }} + disableSubmit={link == ""} + > + + { setLink(text) }} + /> + + ) } \ No newline at end of file diff --git a/components/task/sectionLinkTask.tsx b/components/task/sectionLinkTask.tsx new file mode 100644 index 0000000..b617ff5 --- /dev/null +++ b/components/task/sectionLinkTask.tsx @@ -0,0 +1,122 @@ +import Styles from "@/constants/Styles"; +import { apiDeleteLinkTask, apiGetTaskOne } from "@/lib/api"; +import { urlCompleted } from "@/lib/fun_urlCompleted"; +import { setUpdateTask } from "@/lib/taskUpdate"; +import { useAuthSession } from "@/providers/AuthProvider"; +import { Feather, Ionicons } from "@expo/vector-icons"; +import { useLocalSearchParams } from "expo-router"; +import { useEffect, useState } from "react"; +import { Linking, View } from "react-native"; +import Toast from "react-native-toast-message"; +import { useDispatch, useSelector } from "react-redux"; +import AlertKonfirmasi from "../alertKonfirmasi"; +import BorderBottomItem from "../borderBottomItem"; +import DrawerBottom from "../drawerBottom"; +import MenuItemRow from "../menuItemRow"; +import Text from "../Text"; + +type Props = { + id: string + link: string +} + +export default function SectionLinkTask({ refreshing }: { refreshing: boolean }) { + const [isModal, setModal] = useState(false) + const { token, decryptToken } = useAuthSession() + const { detail } = useLocalSearchParams<{ detail: string }>() + const [data, setData] = useState([]) + const update = useSelector((state: any) => state.taskUpdate) + const dispatch = useDispatch() + const [selectLink, setSelectLink] = useState(null) + + async function handleLoad() { + try { + const hasil = await decryptToken(String(token?.current)) + const response = await apiGetTaskOne({ id: detail, user: hasil, cat: 'link' }) + setData(response.data) + } catch (error) { + console.error(error) + } + } + + useEffect(() => { + handleLoad() + }, [update.link]) + + useEffect(() => { + if (refreshing) + handleLoad(); + }, [refreshing]); + + async function handleDelete() { + try { + const hasil = await decryptToken(String(token?.current)); + const response = await apiDeleteLinkTask({ user: hasil, idLink: String(selectLink?.id) }, String(detail)); + if (response.success) { + Toast.show({ type: 'small', text1: 'Berhasil menghapus link', }) + dispatch(setUpdateTask({ ...update, link: !update.link })) + } else { + Toast.show({ type: 'small', text1: response.message, }) + } + } catch (error) { + console.error(error); + Toast.show({ type: 'small', text1: 'Terjadi kesalahan', }) + } finally { + setModal(false) + } + } + + return ( + <> + { + data.length > 0 && + <> + + Link + + { + data.map((item, index) => { + return ( + } + title={item.link} + titleWeight="normal" + onPress={() => { setSelectLink(item); setModal(true) }} + width={65} + /> + ) + }) + } + + + + + + } + title="Buka Link" + onPress={() => { + Linking.openURL(urlCompleted(String(selectLink?.link))) + }} + /> + } + title="Hapus" + onPress={() => { + setModal(false) + AlertKonfirmasi({ + title: 'Konfirmasi', + desc: 'Apakah Anda yakin ingin menghapus link ini? Link yang dihapus tidak dapat dikembalikan', + onPress: () => { handleDelete() } + }) + }} + /> + + + + } + + ) +} \ No newline at end of file diff --git a/lib/api.ts b/lib/api.ts index a207b7f..aa71619 100644 --- a/lib/api.ts +++ b/lib/api.ts @@ -150,7 +150,7 @@ export const apiGetUser = async ({ user, active, search, group, page }: { user: }; -export const apiCreateUser = async ({data}: {data: FormData}) => { +export const apiCreateUser = async ({ data }: { data: FormData }) => { const response = await api.post('/mobile/user', data, { headers: { 'Content-Type': 'multipart/form-data', @@ -264,7 +264,7 @@ export const apiGetProject = async ({ user, status, search, group, kategori, pag return response.data; }; -export const apiGetProjectOne = async ({ user, cat, id }: { user: string, cat: 'data' | 'progress' | 'task' | 'file' | 'member', id: string }) => { +export const apiGetProjectOne = async ({ user, cat, id }: { user: string, cat: 'data' | 'progress' | 'task' | 'file' | 'member' | 'link', id: string }) => { const response = await api.get(`mobile/project/${id}?user=${user}&cat=${cat}`); return response.data; }; @@ -329,6 +329,11 @@ export const apiDeleteProject = async (data: { user: string }, id: string) => { return response.data; }; +export const apiAddLinkProject = async (data: { user: string, link: string }, id: string) => { + const response = await api.post(`/mobile/project/${id}/link`, data) + return response.data; +}; + export const apiAddFileProject = async ({ data, id }: { data: FormData, id: string }) => { const response = await api.post(`/mobile/project/file/${id}`, data, { @@ -356,6 +361,11 @@ export const apiDeleteFileProject = async (data: { user: string }, id: string) = return response.data; }; +export const apiDeleteLinkProject = async (data: { idLink: string, user: string }, id: string) => { + const response = await api.delete(`/mobile/project/${id}/link`, { data }) + return response.data; +}; + export const apiGetDivision = async ({ user, search, group, kategori, active, page }: { user: string, search: string, group?: string, kategori?: string, active?: string, page?: number }) => { const response = await api.get(`mobile/division?user=${user}&active=${active}&group=${group}&search=${search}&cat=${kategori}&page=${page}`); return response.data; @@ -502,7 +512,7 @@ export const apiGetTask = async ({ user, status, search, division, page }: { use return response.data; }; -export const apiGetTaskOne = async ({ user, cat, id }: { user: string, cat: 'data' | 'progress' | 'task' | 'file' | 'member', id: string }) => { +export const apiGetTaskOne = async ({ user, cat, id }: { user: string, cat: 'data' | 'progress' | 'task' | 'file' | 'member' | 'link', id: string }) => { const response = await api.get(`mobile/task/${id}?user=${user}&cat=${cat}`); return response.data; }; @@ -532,6 +542,11 @@ export const apiDeleteFileTask = async (data: { user: string }, id: string) => { return response.data; }; +export const apiDeleteLinkTask = async (data: { user: string, idLink: string }, id: string) => { + const response = await api.delete(`/mobile/task/${id}/link`, { data }) + return response.data; +}; + export const apiDeleteTaskMember = async (data: { user: string, idUser: string }, id: string) => { const response = await api.delete(`mobile/task/${id}/member`, { data }) return response.data @@ -593,6 +608,11 @@ export const apiDeleteTask = async (data: { user: string }, id: string) => { return response.data; }; +export const apiAddLinkTask = async (data: { user: string, link: string, idDivision: string }, id: string) => { + const response = await api.post(`/mobile/task/${id}/link`, data) + return response.data; +}; + export const apiGetDocument = async ({ user, path, division, category }: { user: string, path: string, division: string, category: 'all' | 'folder' }) => { const response = await api.get(`mobile/document?user=${user}&path=${path}&division=${division}&category=${category}`); return response.data; diff --git a/lib/fun_urlCompleted.ts b/lib/fun_urlCompleted.ts new file mode 100644 index 0000000..825c89f --- /dev/null +++ b/lib/fun_urlCompleted.ts @@ -0,0 +1,4 @@ +export function urlCompleted(url: string) { + if (url.startsWith('http://') || url.startsWith('https://')) return url + return 'https://' + url +} \ No newline at end of file diff --git a/lib/projectUpdate.ts b/lib/projectUpdate.ts index e398a44..8174441 100644 --- a/lib/projectUpdate.ts +++ b/lib/projectUpdate.ts @@ -7,7 +7,8 @@ const projectUpdate = createSlice({ progress: false, task: false, file: false, - member: false + member: false, + link: false, }, reducers: { setUpdateProject: (state, action) => { diff --git a/lib/taskUpdate.ts b/lib/taskUpdate.ts index d4a7daf..2e7fbf0 100644 --- a/lib/taskUpdate.ts +++ b/lib/taskUpdate.ts @@ -7,7 +7,8 @@ const taskUpdate = createSlice({ progress: false, task: false, file: false, - member: false + member: false, + link: false, }, reducers: { setUpdateTask: (state, action) => {