diff --git a/.gitignore b/.gitignore index e9af40e..733c661 100644 --- a/.gitignore +++ b/.gitignore @@ -32,6 +32,9 @@ yarn-error.* # local env files .env*.local +#env +.env + # typescript *.tsbuildinfo diff --git a/app.config.js b/app.config.js new file mode 100644 index 0000000..fae5d7b --- /dev/null +++ b/app.config.js @@ -0,0 +1,72 @@ +import 'dotenv/config'; + +export default { + expo: { + name: "mobile-darmasaba", + slug: "mobile-darmasaba", + version: "1.0.0", + jsEngine: "jsc", + orientation: "portrait", + icon: "./assets/images/icon.png", + scheme: "myapp", + userInterfaceStyle: "automatic", + newArchEnabled: false, + ios: { + supportsTablet: true, + bundleIdentifier: "mobiledarmasaba.app", + infoPlist: { + ITSAppUsesNonExemptEncryption: false + }, + googleServicesFile: "./ios/mobiledarmasaba/GoogleService-Info.plist" + }, + android: { + package: "mobiledarmasaba.app", + adaptiveIcon: { + foregroundImage: "./assets/images/splash-icon.png", + backgroundColor: "#ffffff" + }, + googleServicesFile: "./google-services.json" + }, + web: { + bundler: "metro", + output: "static", + favicon: "./assets/images/favicon.png" + }, + plugins: [ + "expo-router", + [ + "expo-splash-screen", + { + image: "./assets/images/splash-icon.png", + imageWidth: 200, + resizeMode: "contain", + backgroundColor: "#ffffff" + } + ], + "expo-font", + "expo-image-picker", + "expo-web-browser", + [ + "@react-native-firebase/app", + { + ios: { + googleServicesFile: "./ios/mobiledarmasaba/GoogleService-Info.plist" + } + } + ] + ], + experiments: { + typedRoutes: true + }, + extra: { + router: {}, + eas: { + projectId: "cfe34fb8-da8c-4004-b5c6-29d07df75cf2" + }, + URL_API: process.env.URL_API, + URL_OTP: process.env.URL_OTP, + URL_STORAGE : process.env.URL_STORAGE, + URL_FIREBASE_DB : process.env.URL_FIREBASE_DB + } + } +}; diff --git a/app.json b/app.json.bak similarity index 100% rename from app.json rename to app.json.bak 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 344b118..b7edf09 100644 --- a/app/(application)/division/[id]/(fitur-division)/task/[detail]/index.tsx +++ b/app/(application)/division/[id]/(fitur-division)/task/[detail]/index.tsx @@ -5,6 +5,7 @@ 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 SectionReportTask from "@/components/task/sectionReportTask"; import SectionTanggalTugasTask from "@/components/task/sectionTanggalTugasTask"; import Styles from "@/constants/Styles"; import { apiGetTaskOne } from "@/lib/api"; @@ -89,10 +90,11 @@ export default function DetailTaskDivision() { data?.reason != null && data?.reason != "" && } - - - - + + + + + diff --git a/app/(application)/division/[id]/(fitur-division)/task/[detail]/report.tsx b/app/(application)/division/[id]/(fitur-division)/task/[detail]/report.tsx new file mode 100644 index 0000000..f9a5239 --- /dev/null +++ b/app/(application)/division/[id]/(fitur-division)/task/[detail]/report.tsx @@ -0,0 +1,129 @@ +import ButtonBackHeader from "@/components/buttonBackHeader"; +import ButtonSaveHeader from "@/components/buttonSaveHeader"; +import { InputForm } from "@/components/inputForm"; +import Styles from "@/constants/Styles"; +import { apiGetTaskOne, apiReportTask } from "@/lib/api"; +import { setUpdateTask } from "@/lib/taskUpdate"; +import { useAuthSession } from "@/providers/AuthProvider"; +import { router, Stack, useLocalSearchParams } from "expo-router"; +import { useEffect, useState } from "react"; +import { SafeAreaView, ScrollView, View } from "react-native"; +import Toast from "react-native-toast-message"; +import { useDispatch, useSelector } from "react-redux"; + +export default function TaskDivisionReport() { + const { id, detail } = useLocalSearchParams<{ id: string; detail: string }>(); + const { token, decryptToken } = useAuthSession(); + const [laporan, setLaporan] = useState(""); + const [error, setError] = useState(false); + const [disable, setDisable] = useState(false); + const dispatch = useDispatch(); + const update = useSelector((state: any) => state.taskUpdate); + const [loading, setLoading] = useState(false) + + async function handleLoad() { + try { + const hasil = await decryptToken(String(token?.current)); + const response = await apiGetTaskOne({ + user: hasil, + cat: "data", + id: detail, + }); + setLaporan(response.data.report); + } catch (error) { + console.error(error); + } + } + + useEffect(() => { + handleLoad(); + }, []); + + function onValidation(val: string) { + setLaporan(val); + if (val == "" || val == "null") { + setError(true); + } else { + setError(false); + } + } + + function checkAll() { + if (laporan == "" || laporan == "null" || laporan == undefined || laporan == null || error) { + setDisable(true); + } else { + setDisable(false); + } + } + + useEffect(() => { + checkAll(); + }, [laporan, error]); + + async function handleUpdate() { + try { + setLoading(true) + const hasil = await decryptToken(String(token?.current)); + const response = await apiReportTask( + { + report: laporan, + user: hasil, + }, + detail + ); + if (response.success) { + dispatch(setUpdateTask({ ...update, report: !update.report })); + Toast.show({ type: 'small', text1: 'Berhasil mengubah data', }) + router.back(); + } else { + Toast.show({ type: 'small', text1: response.message, }) + } + } catch (error) { + console.error(error); + Toast.show({ type: 'small', text1: 'Terjadi kesalahan', }) + } finally { + setLoading(false) + } + } + + return ( + + ( + { + router.back(); + }} + /> + ), + headerTitle: "Laporan Kegiatan", + headerTitleAlign: "center", + headerRight: () => ( + { handleUpdate() }} + /> + ), + }} + /> + + + { onValidation(val) }} + error={error} + errorText="Laporan kegiatan harus diisi" + multiline + /> + + + + ); +} diff --git a/app/(application)/project/[id]/add-task.tsx b/app/(application)/project/[id]/add-task.tsx index 640817a..78e8f3c 100644 --- a/app/(application)/project/[id]/add-task.tsx +++ b/app/(application)/project/[id]/add-task.tsx @@ -6,14 +6,16 @@ import Styles from "@/constants/Styles"; import { apiCreateProjectTask } from "@/lib/api"; import { setUpdateProject } from "@/lib/projectUpdate"; import { useAuthSession } from "@/providers/AuthProvider"; -import 'intl'; -import 'intl/locale-data/jsonp/id'; +import { useHeaderHeight } from '@react-navigation/elements'; import dayjs from "dayjs"; import { router, Stack, useLocalSearchParams } from "expo-router"; +import 'intl'; +import 'intl/locale-data/jsonp/id'; import { useEffect, useState } from "react"; import { KeyboardAvoidingView, Platform, + Pressable, SafeAreaView, ScrollView, View @@ -23,7 +25,6 @@ import DateTimePicker, { DateType } from "react-native-ui-datepicker"; import { useDispatch, useSelector } from "react-redux"; -import { useHeaderHeight } from '@react-navigation/elements'; export default function ProjectAddTask() { const headerHeight = useHeaderHeight(); @@ -162,6 +163,10 @@ export default function ProjectAddTask() { { (error.endDate || error.startDate) && Tanggal tidak boleh kosong } + {/* TODO */} + + Detail + } + diff --git a/app/(application)/project/[id]/report.tsx b/app/(application)/project/[id]/report.tsx new file mode 100644 index 0000000..8455332 --- /dev/null +++ b/app/(application)/project/[id]/report.tsx @@ -0,0 +1,128 @@ +import ButtonBackHeader from "@/components/buttonBackHeader"; +import ButtonSaveHeader from "@/components/buttonSaveHeader"; +import { InputForm } from "@/components/inputForm"; +import Styles from "@/constants/Styles"; +import { apiGetProjectOne, apiReportProject } from "@/lib/api"; +import { setUpdateProject } from "@/lib/projectUpdate"; +import { useAuthSession } from "@/providers/AuthProvider"; +import { router, Stack, useLocalSearchParams } from "expo-router"; +import { useEffect, useState } from "react"; +import { SafeAreaView, ScrollView, View } from "react-native"; +import Toast from "react-native-toast-message"; +import { useDispatch, useSelector } from "react-redux"; + +export default function ReportProject() { + const { token, decryptToken } = useAuthSession(); + const { id } = useLocalSearchParams<{ id: string }>(); + const dispatch = useDispatch() + const update = useSelector((state: any) => state.projectUpdate) + const [laporan, setLaporan] = useState(""); + const [error, setError] = useState(false); + const [disable, setDisable] = useState(false); + const [loading, setLoading] = useState(false) + + async function handleLoad() { + try { + const hasil = await decryptToken(String(token?.current)); + const response = await apiGetProjectOne({ + user: hasil, + cat: "data", + id: id, + }); + setLaporan(response.data.report); + } catch (error) { + console.error(error); + } + } + + useEffect(() => { + handleLoad(); + }, []); + + function onValidation(val: string) { + setLaporan(val) + if (val == "" || val == "null") { + setError(true) + } else { + setError(false) + } + } + + function checkAll() { + if (laporan == "" || laporan == "null" || laporan == null || laporan == undefined || error) { + setDisable(true) + } else { + setDisable(false) + } + } + + useEffect(() => { + checkAll() + }, [laporan, error]); + + async function handleUpdate() { + try { + setLoading(true) + const hasil = await decryptToken(String(token?.current)); + const response = await apiReportProject({ + report: laporan, + user: hasil, + }, id); + if (response.success) { + dispatch(setUpdateProject({ ...update, report: !update.report })) + Toast.show({ type: 'small', text1: 'Berhasil mengubah data', }) + router.back(); + } else { + Toast.show({ type: 'small', text1: response.message, }) + } + } catch (error) { + console.error(error); + Toast.show({ type: 'small', text1: 'Terjadi kesalahan', }) + } finally { + setLoading(false) + } + } + + + + return ( + + ( + { + router.back(); + }} + /> + ), + headerTitle: "Laporan Kegiatan", + headerTitleAlign: "center", + headerRight: () => ( + { handleUpdate() }} + /> + ), + }} + /> + + + { onValidation(val) }} + error={error} + errorText="Judul Kegiatan harus diisi" + multiline + /> + + + + ); +} diff --git a/app/(application)/project/create/task.tsx b/app/(application)/project/create/task.tsx index e4723b1..f916b4d 100644 --- a/app/(application)/project/create/task.tsx +++ b/app/(application)/project/create/task.tsx @@ -4,6 +4,7 @@ import { InputForm } from "@/components/inputForm"; import Text from "@/components/Text"; import Styles from "@/constants/Styles"; import { setTaskCreate } from "@/lib/taskCreate"; +import { useHeaderHeight } from '@react-navigation/elements'; import dayjs from "dayjs"; import { router, Stack } from "expo-router"; import 'intl'; @@ -20,7 +21,6 @@ import DateTimePicker, { DateType } from "react-native-ui-datepicker"; import { useDispatch, useSelector } from "react-redux"; -import { useHeaderHeight } from '@react-navigation/elements'; export default function CreateProjectAddTask() { const headerHeight = useHeaderHeight(); diff --git a/bun.lockb b/bun.lockb index 3bae3c9..c827a4d 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/components/modalFloat.tsx b/components/modalFloat.tsx index cd162e2..036d2ed 100644 --- a/components/modalFloat.tsx +++ b/components/modalFloat.tsx @@ -8,7 +8,7 @@ type Props = { setVisible: (value: boolean) => void title?: string children: React.ReactNode - onSubmit: () => void + onSubmit?: () => void disableSubmit?: boolean buttonHide?: boolean } diff --git a/components/project/headerProjectDetail.tsx b/components/project/headerProjectDetail.tsx index 57b0c7a..d19e629 100644 --- a/components/project/headerProjectDetail.tsx +++ b/components/project/headerProjectDetail.tsx @@ -67,7 +67,7 @@ export default function HeaderRightProjectDetail({ id, status }: Props) { return ( <> { setVisible(true) }} /> - + } @@ -102,29 +102,46 @@ export default function HeaderRightProjectDetail({ id, status }: Props) { disabled={status == 3} /> + + } + title="Laporan" + onPress={() => { + if (status == 3) return + setVisible(false) + router.push(`/project/${id}/report`) + }} + disabled={status == 3} + /> + { + entityUser.role != "user" && entityUser.role != "coadmin" && + <> + } + title="Tambah Anggota" + onPress={() => { + if (status == 3) return + setVisible(false) + router.push(`/project/${id}/add-member`) + }} + disabled={status == 3} + /> + } + title="Edit" + onPress={() => { + if (status == 3) return + setVisible(false) + router.push(`/project/${id}/edit`) + }} + disabled={status == 3} + /> + + } + { entityUser.role != "user" && entityUser.role != "coadmin" && - } - title="Tambah Anggota" - onPress={() => { - if (status == 3) return - setVisible(false) - router.push(`/project/${id}/add-member`) - }} - disabled={status == 3} - /> - } - title="Edit" - onPress={() => { - if (status == 3) return - setVisible(false) - router.push(`/project/${id}/edit`) - }} - disabled={status == 3} - /> { status == 3 ? diff --git a/components/project/modalListDetailTugasProject.tsx b/components/project/modalListDetailTugasProject.tsx new file mode 100644 index 0000000..8c665c5 --- /dev/null +++ b/components/project/modalListDetailTugasProject.tsx @@ -0,0 +1,116 @@ +import Styles from "@/constants/Styles"; +import { apiGetProjectTask } from "@/lib/api"; +import { useAuthSession } from "@/providers/AuthProvider"; +import { useEffect, useState } from "react"; +import { Dimensions, View, VirtualizedList } from "react-native"; +import { InputDate } from "../inputDate"; +import ModalFloat from "../modalFloat"; +import Skeleton from "../skeleton"; +import Text from "../Text"; + +interface Props { + id: string; + date: string; + timeStart: string; + timeEnd: string; +} + +export default function ModalListDetailTugasProject({ isVisible, setVisible, idTask }: { isVisible: boolean, setVisible: (value: boolean) => void, idTask: string }) { + const [data, setData] = useState([]) + const [loading, setLoading] = useState(false) + const { token, decryptToken } = useAuthSession() + const arrSkeleton = Array.from({ length: 3 }, (_, index) => index) + const tinggiScreen = Dimensions.get("window").height; + const tinggiFix = tinggiScreen * 70 / 100; + + + + async function getData() { + try { + setLoading(true) + const hasil = await decryptToken(String(token?.current)); + const res = await apiGetProjectTask({ user: hasil, id: idTask, cat: "detailTask" }) + setData(res.data) + } catch (error) { + console.error(error) + } finally { + setLoading(false) + } + } + + useEffect(() => { + if (isVisible) { + getData() + } + }, [isVisible, idTask]) + + const getItem = (_data: unknown, index: number): Props => ({ + id: data[index].id, + date: data[index].date, + timeStart: data[index].timeStart, + timeEnd: data[index].timeEnd, + }) + + return ( + + + { + loading ? + arrSkeleton.map((item: any, i: number) => { + return ( + + ) + }) + : + data.length > 0 ? + ( + data.length} + getItem={getItem} + renderItem={({ item, index }: { item: Props, index: number }) => { + return ( + + {item.date} + + + { }} + value={item.timeStart} + label="Waktu Awal" + placeholder="--:--" + /> + + + { }} + mode="time" + value={item.timeEnd} + label="Waktu Akhir" + placeholder="--:--" + disable + /> + + + + ) + }} + keyExtractor={(item, index) => String(index)} + showsVerticalScrollIndicator={false} + /> + ) + : + Tidak ada data + } + + + + ) +} \ No newline at end of file diff --git a/components/project/sectionFile.tsx b/components/project/sectionFile.tsx index 524b560..77311f4 100644 --- a/components/project/sectionFile.tsx +++ b/components/project/sectionFile.tsx @@ -90,12 +90,6 @@ export default function SectionFile({ status, member, refreshing }: { status: nu } } - // async function download() { - // const destination = new Directory(Paths.document, 'pdfs'); - // const filename = "dummy.pdf"; - // const result = await File.downloadFileAsync('https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf', destination); - // } - const openFile = () => { setModal(false) diff --git a/components/project/sectionReportProject.tsx b/components/project/sectionReportProject.tsx new file mode 100644 index 0000000..18e7b82 --- /dev/null +++ b/components/project/sectionReportProject.tsx @@ -0,0 +1,55 @@ +import Styles from "@/constants/Styles"; +import { apiGetProjectOne } from "@/lib/api"; +import { useAuthSession } from "@/providers/AuthProvider"; +import { useLocalSearchParams } from "expo-router"; +import { useEffect, useState } from "react"; +import { View } from "react-native"; +import { useSelector } from "react-redux"; +import Text from "../Text"; +import TextExpandable from "../textExpandable"; + +export default function SectionReportProject({ refreshing }: { refreshing?: boolean }) { + const update = useSelector((state: any) => state.projectUpdate) + const { token, decryptToken } = useAuthSession(); + const { id } = useLocalSearchParams<{ id: string }>(); + const [data, setData] = useState(""); + + async function handleLoad() { + try { + const hasil = await decryptToken(String(token?.current)); + const response = await apiGetProjectOne({ + user: hasil, + cat: "data", + id: id, + }); + setData(response.data.report); + } catch (error) { + console.error(error); + } + } + + useEffect(() => { + handleLoad(); + }, [update.report]); + + useEffect(() => { + if (refreshing) + handleLoad(); + }, [refreshing]); + + return ( + <> + { + data != "" && data != null && + + + Laporan Kegiatan + + + + + + } + + ); +} diff --git a/components/project/sectionTanggalTugas.tsx b/components/project/sectionTanggalTugas.tsx index ee3cb40..285b55e 100644 --- a/components/project/sectionTanggalTugas.tsx +++ b/components/project/sectionTanggalTugas.tsx @@ -15,6 +15,7 @@ import MenuItemRow from "../menuItemRow"; import ModalSelect from "../modalSelect"; import SkeletonTask from "../skeletonTask"; import Text from "../Text"; +import ModalListDetailTugasProject from "./modalListDetailTugasProject"; type Props = { id: string; @@ -33,6 +34,7 @@ export default function SectionTanggalTugasProject({ status, member, refreshing const [isModal, setModal] = useState(false); const [isSelect, setSelect] = useState(false); const { token, decryptToken } = useAuthSession(); + const [modalDetail, setModalDetail] = useState(false) const { id } = useLocalSearchParams<{ id: string }>(); const [data, setData] = useState([]); const [loading, setLoading] = useState(true) @@ -188,6 +190,24 @@ export default function SectionTanggalTugasProject({ status, member, refreshing }} /> + + } + title="Detail Waktu" + onPress={() => { + setModal(false); + setTimeout(() => { + setModalDetail(true) + }, 600) + }} + /> + + } title="Hapus Tugas" @@ -213,6 +233,12 @@ export default function SectionTanggalTugasProject({ status, member, refreshing open={isSelect} valChoose={String(tugas.status)} /> + + ); } diff --git a/components/task/headerTaskDetail.tsx b/components/task/headerTaskDetail.tsx index 816738f..392c6da 100644 --- a/components/task/headerTaskDetail.tsx +++ b/components/task/headerTaskDetail.tsx @@ -101,7 +101,7 @@ export default function HeaderRightTaskDetail({ id, division, status }: Props) { : { setVisible(true) }} /> } - + } @@ -137,31 +137,49 @@ export default function HeaderRightTaskDetail({ id, division, status }: Props) { disabled={status == 3} /> + + } + title="Laporan" + onPress={() => { + if (status == 3) return + setVisible(false) + router.push(`./${id}/report`) + }} + disabled={status == 3} + /> + { + ((entityUser.role != "user" && entityUser.role != "coadmin") || isAdminDivision) + && + <> + } + title="Tambah Anggota" + onPress={() => { + if (status == 3) return + setVisible(false) + router.push(`./${id}/add-member`) + }} + disabled={status == 3} + /> + } + title="Edit" + onPress={() => { + if (status == 3) return + setVisible(false) + router.push(`./${id}/edit`) + }} + disabled={status == 3} + /> + + } + { ((entityUser.role != "user" && entityUser.role != "coadmin") || isAdminDivision) && - } - title="Tambah Anggota" - onPress={() => { - if (status == 3) return - setVisible(false) - router.push(`./${id}/add-member`) - }} - disabled={status == 3} - /> - } - title="Edit" - onPress={() => { - if (status == 3) return - setVisible(false) - router.push(`./${id}/edit`) - }} - disabled={status == 3} - /> { status == 3 ? @@ -190,7 +208,7 @@ export default function HeaderRightTaskDetail({ id, division, status }: Props) { } } - + state.taskUpdate) + const { token, decryptToken } = useAuthSession() + const { id, detail } = useLocalSearchParams<{ id: string, detail: string }>(); + const [data, setData] = useState('') + + async function handleLoad() { + try { + const hasil = await decryptToken(String(token?.current)) + const response = await apiGetTaskOne({ id: detail, user: hasil, cat: 'data' }) + setData(response.data.report) + } catch (error) { + console.error(error) + } + } + + useEffect(() => { + handleLoad() + }, [update.report]) + + useEffect(() => { + if (refreshing) + handleLoad(); + }, [refreshing]); + + + return ( + <> + { + data != "" && data != null && + + + Laporan Kegiatan + + + + + + } + + ) +} \ No newline at end of file diff --git a/components/textExpandable.tsx b/components/textExpandable.tsx new file mode 100644 index 0000000..13e4413 --- /dev/null +++ b/components/textExpandable.tsx @@ -0,0 +1,83 @@ +import Styles from "@/constants/Styles"; +import { useRef, useState, useEffect } from "react"; +import { Animated, Pressable, View } from "react-native"; +import Text from "./Text"; + +export default function TextExpandable({ content, maxLines }: { content: string, maxLines: number }) { + const [isExpanded, setIsExpanded] = useState(false); + const [shouldShowMore, setShouldShowMore] = useState(false); + const [collapsedHeight, setCollapsedHeight] = useState(0); + const [fullHeight, setFullHeight] = useState(0); + const animatedHeight = useRef(new Animated.Value(0)).current; + + const measureCollapsed = (e: any) => { + if (collapsedHeight === 0) { + setCollapsedHeight(e.nativeEvent.layout.height); + animatedHeight.setValue(e.nativeEvent.layout.height); + } + }; + + const measureFull = (e: any) => { + if (fullHeight === 0) { + setFullHeight(e.nativeEvent.layout.height); + } + }; + + // Cek apakah memang perlu "View More" + useEffect(() => { + if (collapsedHeight > 0 && fullHeight > 0) { + setShouldShowMore(fullHeight > collapsedHeight + 1); // +1 untuk toleransi float + } + }, [collapsedHeight, fullHeight]); + + const toggleExpand = () => { + Animated.timing(animatedHeight, { + toValue: isExpanded ? collapsedHeight : fullHeight, + duration: 300, + useNativeDriver: false, + }).start(); + setIsExpanded(!isExpanded); + }; + + return ( + + {/* Hidden full text for measurement */} + + + {content} + + + + {/* Collapsed text for measurement */} + + + {content} + + + + {/* Animated visible text */} + + + {content} + + + + {shouldShowMore && ( + + + {isExpanded ? 'View Less' : 'View More'} + + + )} + + ); +}; diff --git a/constants/Colors.ts b/constants/Colors.ts index 54c7695..be9eec1 100644 --- a/constants/Colors.ts +++ b/constants/Colors.ts @@ -1,8 +1,3 @@ -/** - * Below are the colors that are used in the app. The colors are defined in the light and dark mode. - * There are many other ways to style your app. For example, [Nativewind](https://www.nativewind.dev/), [Tamagui](https://tamagui.dev/), [unistyles](https://reactnativeunistyles.vercel.app), etc. - */ - const tintColorLight = '#19345E'; const tintColorDark = '#fff'; diff --git a/constants/ConstEnv.ts b/constants/ConstEnv.ts new file mode 100644 index 0000000..65de1b0 --- /dev/null +++ b/constants/ConstEnv.ts @@ -0,0 +1,5 @@ +import Constants from 'expo-constants'; + +export const ConstEnv = { + url_storage : Constants?.expoConfig?.extra?.URL_STORAGE +} \ No newline at end of file diff --git a/constants/Styles.ts b/constants/Styles.ts index 4af2ba9..88b95a3 100644 --- a/constants/Styles.ts +++ b/constants/Styles.ts @@ -45,8 +45,7 @@ const Styles = StyleSheet.create({ fontWeight: 'bold', }, textLink: { - lineHeight: 30, - fontSize: 16, + fontSize: 14, color: '#0a7ea4', }, textInformation: { @@ -270,6 +269,12 @@ const Styles = StyleSheet.create({ flexDirection: 'row', justifyContent: 'center' }, + btnLainnya: { + alignSelf: 'flex-start', + backgroundColor: '#19345E', + paddingVertical: 5, + marginVertical: 5 + }, btnMenuRow: { width: '33%', alignItems: 'center' @@ -593,6 +598,11 @@ const Styles = StyleSheet.create({ bottom: 5, right: 5, position: 'absolute' + }, + hidden: { + position: 'absolute', + opacity: 0, + zIndex: -1, } }) diff --git a/ios/mobiledarmasaba.xcodeproj/project.pbxproj b/ios/mobiledarmasaba.xcodeproj/project.pbxproj index 9ce7135..0d873ad 100644 --- a/ios/mobiledarmasaba.xcodeproj/project.pbxproj +++ b/ios/mobiledarmasaba.xcodeproj/project.pbxproj @@ -252,8 +252,6 @@ "$(BUILT_PRODUCTS_DIR)/$(INFOPLIST_PATH)", ); name = "[CP-User] [RNFB] Core Configuration"; - outputPaths = ( - ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "#!/usr/bin/env bash\n#\n# Copyright (c) 2016-present Invertase Limited & Contributors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this library except in compliance with the License.\n# You may obtain a copy of the License at\n#\n# http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n#\n\n##########################################################################\n##########################################################################\n#\n# NOTE THAT IF YOU CHANGE THIS FILE YOU MUST RUN pod install AFTERWARDS\n#\n# This file is installed as an Xcode build script in the project file\n# by cocoapods, and you will not see your changes until you pod install\n#\n##########################################################################\n##########################################################################\n\nset -e\n\n_MAX_LOOKUPS=2;\n_SEARCH_RESULT=''\n_RN_ROOT_EXISTS=''\n_CURRENT_LOOKUPS=1\n_JSON_ROOT=\"'react-native'\"\n_JSON_FILE_NAME='firebase.json'\n_JSON_OUTPUT_BASE64='e30=' # { }\n_CURRENT_SEARCH_DIR=${PROJECT_DIR}\n_PLIST_BUDDY=/usr/libexec/PlistBuddy\n_TARGET_PLIST=\"${BUILT_PRODUCTS_DIR}/${INFOPLIST_PATH}\"\n_DSYM_PLIST=\"${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}/Contents/Info.plist\"\n\n# plist arrays\n_PLIST_ENTRY_KEYS=()\n_PLIST_ENTRY_TYPES=()\n_PLIST_ENTRY_VALUES=()\n\nfunction setPlistValue {\n echo \"info: setting plist entry '$1' of type '$2' in file '$4'\"\n ${_PLIST_BUDDY} -c \"Add :$1 $2 '$3'\" $4 || echo \"info: '$1' already exists\"\n}\n\nfunction getFirebaseJsonKeyValue () {\n if [[ ${_RN_ROOT_EXISTS} ]]; then\n ruby -Ku -e \"require 'rubygems';require 'json'; output=JSON.parse('$1'); puts output[$_JSON_ROOT]['$2']\"\n else\n echo \"\"\n fi;\n}\n\nfunction jsonBoolToYesNo () {\n if [[ $1 == \"false\" ]]; then\n echo \"NO\"\n elif [[ $1 == \"true\" ]]; then\n echo \"YES\"\n else echo \"NO\"\n fi\n}\n\necho \"info: -> RNFB build script started\"\necho \"info: 1) Locating ${_JSON_FILE_NAME} file:\"\n\nif [[ -z ${_CURRENT_SEARCH_DIR} ]]; then\n _CURRENT_SEARCH_DIR=$(pwd)\nfi;\n\nwhile true; do\n _CURRENT_SEARCH_DIR=$(dirname \"$_CURRENT_SEARCH_DIR\")\n if [[ \"$_CURRENT_SEARCH_DIR\" == \"/\" ]] || [[ ${_CURRENT_LOOKUPS} -gt ${_MAX_LOOKUPS} ]]; then break; fi;\n echo \"info: ($_CURRENT_LOOKUPS of $_MAX_LOOKUPS) Searching in '$_CURRENT_SEARCH_DIR' for a ${_JSON_FILE_NAME} file.\"\n _SEARCH_RESULT=$(find \"$_CURRENT_SEARCH_DIR\" -maxdepth 2 -name ${_JSON_FILE_NAME} -print | /usr/bin/head -n 1)\n if [[ ${_SEARCH_RESULT} ]]; then\n echo \"info: ${_JSON_FILE_NAME} found at $_SEARCH_RESULT\"\n break;\n fi;\n _CURRENT_LOOKUPS=$((_CURRENT_LOOKUPS+1))\ndone\n\nif [[ ${_SEARCH_RESULT} ]]; then\n _JSON_OUTPUT_RAW=$(cat \"${_SEARCH_RESULT}\")\n _RN_ROOT_EXISTS=$(ruby -Ku -e \"require 'rubygems';require 'json'; output=JSON.parse('$_JSON_OUTPUT_RAW'); puts output[$_JSON_ROOT]\" || echo '')\n\n if [[ ${_RN_ROOT_EXISTS} ]]; then\n if ! python3 --version >/dev/null 2>&1; then echo \"python3 not found, firebase.json file processing error.\" && exit 1; fi\n _JSON_OUTPUT_BASE64=$(python3 -c 'import json,sys,base64;print(base64.b64encode(bytes(json.dumps(json.loads(open('\"'${_SEARCH_RESULT}'\"', '\"'rb'\"').read())['${_JSON_ROOT}']), '\"'utf-8'\"')).decode())' || echo \"e30=\")\n fi\n\n _PLIST_ENTRY_KEYS+=(\"firebase_json_raw\")\n _PLIST_ENTRY_TYPES+=(\"string\")\n _PLIST_ENTRY_VALUES+=(\"$_JSON_OUTPUT_BASE64\")\n\n # config.app_data_collection_default_enabled\n _APP_DATA_COLLECTION_ENABLED=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"app_data_collection_default_enabled\")\n if [[ $_APP_DATA_COLLECTION_ENABLED ]]; then\n _PLIST_ENTRY_KEYS+=(\"FirebaseDataCollectionDefaultEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_APP_DATA_COLLECTION_ENABLED\")\")\n fi\n\n # config.analytics_auto_collection_enabled\n _ANALYTICS_AUTO_COLLECTION=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_auto_collection_enabled\")\n if [[ $_ANALYTICS_AUTO_COLLECTION ]]; then\n _PLIST_ENTRY_KEYS+=(\"FIREBASE_ANALYTICS_COLLECTION_ENABLED\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_AUTO_COLLECTION\")\")\n fi\n\n # config.analytics_collection_deactivated\n _ANALYTICS_DEACTIVATED=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_collection_deactivated\")\n if [[ $_ANALYTICS_DEACTIVATED ]]; then\n _PLIST_ENTRY_KEYS+=(\"FIREBASE_ANALYTICS_COLLECTION_DEACTIVATED\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_DEACTIVATED\")\")\n fi\n\n # config.analytics_idfv_collection_enabled\n _ANALYTICS_IDFV_COLLECTION=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_idfv_collection_enabled\")\n if [[ $_ANALYTICS_IDFV_COLLECTION ]]; then\n _PLIST_ENTRY_KEYS+=(\"GOOGLE_ANALYTICS_IDFV_COLLECTION_ENABLED\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_IDFV_COLLECTION\")\")\n fi\n\n # config.analytics_default_allow_analytics_storage\n _ANALYTICS_STORAGE=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_default_allow_analytics_storage\")\n if [[ $_ANALYTICS_STORAGE ]]; then\n _PLIST_ENTRY_KEYS+=(\"GOOGLE_ANALYTICS_DEFAULT_ALLOW_ANALYTICS_STORAGE\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_STORAGE\")\")\n fi\n\n # config.analytics_default_allow_ad_storage\n _ANALYTICS_AD_STORAGE=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_default_allow_ad_storage\")\n if [[ $_ANALYTICS_AD_STORAGE ]]; then\n _PLIST_ENTRY_KEYS+=(\"GOOGLE_ANALYTICS_DEFAULT_ALLOW_AD_STORAGE\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_AD_STORAGE\")\")\n fi\n\n # config.analytics_default_allow_ad_user_data\n _ANALYTICS_AD_USER_DATA=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_default_allow_ad_user_data\")\n if [[ $_ANALYTICS_AD_USER_DATA ]]; then\n _PLIST_ENTRY_KEYS+=(\"GOOGLE_ANALYTICS_DEFAULT_ALLOW_AD_USER_DATA\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_AD_USER_DATA\")\")\n fi\n\n # config.analytics_default_allow_ad_personalization_signals\n _ANALYTICS_PERSONALIZATION=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_default_allow_ad_personalization_signals\")\n if [[ $_ANALYTICS_PERSONALIZATION ]]; then\n _PLIST_ENTRY_KEYS+=(\"GOOGLE_ANALYTICS_DEFAULT_ALLOW_AD_PERSONALIZATION_SIGNALS\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_PERSONALIZATION\")\")\n fi\n\n # config.analytics_registration_with_ad_network_enabled\n _ANALYTICS_REGISTRATION_WITH_AD_NETWORK=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"google_analytics_registration_with_ad_network_enabled\")\n if [[ $_ANALYTICS_REGISTRATION_WITH_AD_NETWORK ]]; then\n _PLIST_ENTRY_KEYS+=(\"GOOGLE_ANALYTICS_REGISTRATION_WITH_AD_NETWORK_ENABLED\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_REGISTRATION_WITH_AD_NETWORK\")\")\n fi\n\n # config.google_analytics_automatic_screen_reporting_enabled\n _ANALYTICS_AUTO_SCREEN_REPORTING=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"google_analytics_automatic_screen_reporting_enabled\")\n if [[ $_ANALYTICS_AUTO_SCREEN_REPORTING ]]; then\n _PLIST_ENTRY_KEYS+=(\"FirebaseAutomaticScreenReportingEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_AUTO_SCREEN_REPORTING\")\")\n fi\n\n # config.perf_auto_collection_enabled\n _PERF_AUTO_COLLECTION=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"perf_auto_collection_enabled\")\n if [[ $_PERF_AUTO_COLLECTION ]]; then\n _PLIST_ENTRY_KEYS+=(\"firebase_performance_collection_enabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_PERF_AUTO_COLLECTION\")\")\n fi\n\n # config.perf_collection_deactivated\n _PERF_DEACTIVATED=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"perf_collection_deactivated\")\n if [[ $_PERF_DEACTIVATED ]]; then\n _PLIST_ENTRY_KEYS+=(\"firebase_performance_collection_deactivated\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_PERF_DEACTIVATED\")\")\n fi\n\n # config.messaging_auto_init_enabled\n _MESSAGING_AUTO_INIT=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"messaging_auto_init_enabled\")\n if [[ $_MESSAGING_AUTO_INIT ]]; then\n _PLIST_ENTRY_KEYS+=(\"FirebaseMessagingAutoInitEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_MESSAGING_AUTO_INIT\")\")\n fi\n\n # config.in_app_messaging_auto_colllection_enabled\n _FIAM_AUTO_INIT=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"in_app_messaging_auto_collection_enabled\")\n if [[ $_FIAM_AUTO_INIT ]]; then\n _PLIST_ENTRY_KEYS+=(\"FirebaseInAppMessagingAutomaticDataCollectionEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_FIAM_AUTO_INIT\")\")\n fi\n\n # config.app_check_token_auto_refresh\n _APP_CHECK_TOKEN_AUTO_REFRESH=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"app_check_token_auto_refresh\")\n if [[ $_APP_CHECK_TOKEN_AUTO_REFRESH ]]; then\n _PLIST_ENTRY_KEYS+=(\"FirebaseAppCheckTokenAutoRefreshEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_APP_CHECK_TOKEN_AUTO_REFRESH\")\")\n fi\n\n # config.crashlytics_disable_auto_disabler - undocumented for now - mainly for debugging, document if becomes useful\n _CRASHLYTICS_AUTO_DISABLE_ENABLED=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"crashlytics_disable_auto_disabler\")\n if [[ $_CRASHLYTICS_AUTO_DISABLE_ENABLED == \"true\" ]]; then\n echo \"Disabled Crashlytics auto disabler.\" # do nothing\n else\n _PLIST_ENTRY_KEYS+=(\"FirebaseCrashlyticsCollectionEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"NO\")\n fi\nelse\n _PLIST_ENTRY_KEYS+=(\"firebase_json_raw\")\n _PLIST_ENTRY_TYPES+=(\"string\")\n _PLIST_ENTRY_VALUES+=(\"$_JSON_OUTPUT_BASE64\")\n echo \"warning: A firebase.json file was not found, whilst this file is optional it is recommended to include it to configure firebase services in React Native Firebase.\"\nfi;\n\necho \"info: 2) Injecting Info.plist entries: \"\n\n# Log out the keys we're adding\nfor i in \"${!_PLIST_ENTRY_KEYS[@]}\"; do\n echo \" -> $i) ${_PLIST_ENTRY_KEYS[$i]}\" \"${_PLIST_ENTRY_TYPES[$i]}\" \"${_PLIST_ENTRY_VALUES[$i]}\"\ndone\n\nfor plist in \"${_TARGET_PLIST}\" \"${_DSYM_PLIST}\" ; do\n if [[ -f \"${plist}\" ]]; then\n\n # paths with spaces break the call to setPlistValue. temporarily modify\n # the shell internal field separator variable (IFS), which normally\n # includes spaces, to consist only of line breaks\n oldifs=$IFS\n IFS=\"\n\"\n\n for i in \"${!_PLIST_ENTRY_KEYS[@]}\"; do\n setPlistValue \"${_PLIST_ENTRY_KEYS[$i]}\" \"${_PLIST_ENTRY_TYPES[$i]}\" \"${_PLIST_ENTRY_VALUES[$i]}\" \"${plist}\"\n done\n\n # restore the original internal field separator value\n IFS=$oldifs\n else\n echo \"warning: A Info.plist build output file was not found (${plist})\"\n fi\ndone\n\necho \"info: <- RNFB build script finished\"\n"; @@ -489,7 +487,10 @@ LIBRARY_SEARCH_PATHS = "$(SDKROOT)/usr/lib/swift\"$(inherited)\""; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; - OTHER_LDFLAGS = "$(inherited) "; + OTHER_LDFLAGS = ( + "$(inherited)", + " ", + ); REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native"; SDKROOT = iphoneos; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) DEBUG"; @@ -544,7 +545,10 @@ ); LIBRARY_SEARCH_PATHS = "$(SDKROOT)/usr/lib/swift\"$(inherited)\""; MTL_ENABLE_DEBUG_INFO = NO; - OTHER_LDFLAGS = "$(inherited) "; + OTHER_LDFLAGS = ( + "$(inherited)", + " ", + ); REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native"; SDKROOT = iphoneos; USE_HERMES = false; diff --git a/lib/api.ts b/lib/api.ts index aa71619..3a485df 100644 --- a/lib/api.ts +++ b/lib/api.ts @@ -1,9 +1,11 @@ import axios from 'axios'; +import Constants from 'expo-constants'; const api = axios.create({ // baseURL: 'http://10.0.2.2:3000/api', // baseURL: 'https://stg-darmasaba.wibudev.com/api', - baseURL: 'http://192.168.1.110:3000/api', + // baseURL: 'http://192.168.154.198:3000/api', + baseURL: Constants?.expoConfig?.extra?.URL_API }); export const apiCheckPhoneLogin = async (body: { phone: string }) => { @@ -12,7 +14,7 @@ export const apiCheckPhoneLogin = async (body: { phone: string }) => { } export const apiSendOtp = async (body: { phone: string, otp: number }) => { - const res = await axios.get(`https://wa.wibudev.com/code?nom=${body.phone}&text=*DARMASABA*%0A%0A + const res = await axios.get(`${Constants.expoConfig?.extra?.URL_OTP}/code?nom=${body.phone}&text=*DARMASABA*%0A%0A JANGAN BERIKAN KODE RAHASIA ini kepada siapa pun TERMASUK PIHAK DARMASABA. Masukkan otentikasi: *${encodeURIComponent(body.otp)}*`) return res.status } @@ -274,6 +276,11 @@ export const apiEditProject = async (data: { name: string, user: string }, id: s return response.data; }; +export const apiReportProject = async (data: { report: string, user: string }, id: string) => { + const response = await api.put(`/mobile/project/${id}/lainnya`, data) + return response.data; +}; + export const apiCreateProjectTask = async ({ data, id }: { data: { name: string, dateStart: string, user: string, dateEnd: string }, id: string }) => { const response = await api.post(`/mobile/project/${id}`, data) return response.data; @@ -294,8 +301,8 @@ export const apiDeleteProjectTask = async (data: { user: string, idProject: stri return response.data }; -export const apiGetProjectTask = async ({ user, id }: { user: string, id: string }) => { - const response = await api.get(`mobile/project/detail/${id}?user=${user}`); +export const apiGetProjectTask = async ({ user, id, cat }: { user: string, id: string, cat?: string }) => { + const response = await api.get(`mobile/project/detail/${id}?user=${user}${cat ? `&cat=${cat}` : ""}`); return response.data; }; @@ -584,6 +591,11 @@ export const apiEditTask = async (data: { title: string, user: string }, id: str return response.data; }; +export const apiReportTask = async (data: { report: string, user: string }, id: string) => { + const response = await api.put(`/mobile/task/${id}/lainnya`, data) + return response.data; +}; + export const apiCancelTask = async (data: { user: string, reason: string }, id: string) => { const response = await api.delete(`mobile/task/${id}`, { data }) return response.data diff --git a/lib/projectUpdate.ts b/lib/projectUpdate.ts index 8174441..3453957 100644 --- a/lib/projectUpdate.ts +++ b/lib/projectUpdate.ts @@ -9,6 +9,7 @@ const projectUpdate = createSlice({ file: false, member: false, link: false, + report: false, }, reducers: { setUpdateProject: (state, action) => { diff --git a/lib/taskUpdate.ts b/lib/taskUpdate.ts index 2e7fbf0..8f68e53 100644 --- a/lib/taskUpdate.ts +++ b/lib/taskUpdate.ts @@ -9,6 +9,7 @@ const taskUpdate = createSlice({ file: false, member: false, link: false, + report: false, }, reducers: { setUpdateTask: (state, action) => { diff --git a/package.json b/package.json index 61d2a97..815c340 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ "crypto-es": "^2.1.0", "crypto-js": "^3.1.9-1", "dayjs": "^1.11.13", + "dotenv": "^17.2.1", "expo": "^53.0.9", "expo-blur": "~14.1.4", "expo-clipboard": "^7.1.4",