Compare commits
40 Commits
amalia/06-
...
amalia/22-
| Author | SHA1 | Date | |
|---|---|---|---|
| 1dc69b64d2 | |||
| a409bfef2f | |||
| 99c81f6f0d | |||
| 4e6b27bbcc | |||
| 6a97ae76fc | |||
| 40c1fa4ed2 | |||
| e2c8f1db39 | |||
| 72fa18565d | |||
| b0e959e3e1 | |||
| 263875ae55 | |||
| 7810eb1686 | |||
| 0956dea846 | |||
| 2e5698b566 | |||
| 1ee9bea65e | |||
| fa5005a76a | |||
| 7015e92366 | |||
| acc464bfc8 | |||
| edbeb30ebe | |||
| 57a4e2fce6 | |||
| fd1d20bb32 | |||
| e8e5af7126 | |||
| 1089afb6aa | |||
| 0f5a56c612 | |||
| f929791075 | |||
| a49d25500a | |||
| fce5465d4b | |||
| 5ad055f543 | |||
| 3060ea84a5 | |||
| cd16b8ba04 | |||
| b490b93c00 | |||
| 602860d9c3 | |||
| 3efd44ce70 | |||
| 7555ece0fa | |||
| 0c8297f785 | |||
| 9cb848ddd2 | |||
| 81aedb525f | |||
| 24c07efb97 | |||
| 323d31250b | |||
| c119d3e775 | |||
| 9a5765f0d0 |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -32,6 +32,9 @@ yarn-error.*
|
||||
# local env files
|
||||
.env*.local
|
||||
|
||||
#env
|
||||
.env
|
||||
|
||||
# typescript
|
||||
*.tsbuildinfo
|
||||
|
||||
|
||||
73
app.config.js
Normal file
73
app.config.js
Normal file
@@ -0,0 +1,73 @@
|
||||
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,
|
||||
PASS_ENC: process.env.PASS_ENC
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -14,7 +14,8 @@ import { pushToPage } from "@/lib/pushToPage";
|
||||
import store from "@/lib/store";
|
||||
import { useAuthSession } from "@/providers/AuthProvider";
|
||||
import AsyncStorage from "@react-native-async-storage/async-storage";
|
||||
import firebase from '@react-native-firebase/app';
|
||||
import { getApp } from "@react-native-firebase/app";
|
||||
import { getMessaging, onMessage } from "@react-native-firebase/messaging";
|
||||
import { Redirect, router, Stack, usePathname } from "expo-router";
|
||||
import { StatusBar } from 'expo-status-bar';
|
||||
import { useEffect } from "react";
|
||||
@@ -25,10 +26,12 @@ export default function RootLayout() {
|
||||
const { token, decryptToken, isLoading } = useAuthSession()
|
||||
const pathname = usePathname()
|
||||
|
||||
async function handleReadNotification(id: string, category: string, idContent: string) {
|
||||
async function handleReadNotification(id: string, category: string, idContent: string, title: string) {
|
||||
try {
|
||||
const hasil = await decryptToken(String(token?.current))
|
||||
const response = await apiReadOneNotification({ user: hasil, id: id })
|
||||
if (title != "Komentar Baru") {
|
||||
const hasil = await decryptToken(String(token?.current))
|
||||
const response = await apiReadOneNotification({ user: hasil, id: id })
|
||||
}
|
||||
pushToPage(category, idContent)
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
@@ -49,30 +52,33 @@ export default function RootLayout() {
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const unsubscribe = firebase.app().messaging().onMessage(async remoteMessage => {
|
||||
const mess = getMessaging(getApp());
|
||||
|
||||
const unsubscribe = onMessage(mess, async remoteMessage => {
|
||||
const id = remoteMessage?.data?.id;
|
||||
const category = remoteMessage?.data?.category;
|
||||
const content = remoteMessage?.data?.content;
|
||||
if (
|
||||
remoteMessage.notification != undefined &&
|
||||
remoteMessage.notification.title != undefined &&
|
||||
remoteMessage.notification.body != undefined &&
|
||||
pathname != `/${category}/${content}`
|
||||
) {
|
||||
Notifier.showNotification({
|
||||
title: remoteMessage.notification?.title,
|
||||
description: remoteMessage.notification?.body,
|
||||
duration: 3000,
|
||||
animationDuration: 300,
|
||||
showEasing: Easing.ease,
|
||||
onPress: () => handleReadNotification(String(id), String(category), String(content)),
|
||||
hideOnPress: true,
|
||||
});
|
||||
const title = remoteMessage?.notification?.title;
|
||||
|
||||
if (remoteMessage.notification?.title && remoteMessage.notification?.body) {
|
||||
if (category === 'discussion-general' && pathname === '/discussion/' + content) {
|
||||
return null;
|
||||
} else if (pathname !== `/${category}/${content}`) {
|
||||
Notifier.showNotification({
|
||||
title: title,
|
||||
description: remoteMessage.notification?.body,
|
||||
duration: 3000,
|
||||
animationDuration: 300,
|
||||
showEasing: Easing.ease,
|
||||
onPress: () => handleReadNotification(String(id), String(category), String(content), String(title)),
|
||||
hideOnPress: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return unsubscribe;
|
||||
}, []);
|
||||
}, [pathname]);
|
||||
|
||||
|
||||
if (isLoading) {
|
||||
|
||||
@@ -149,9 +149,9 @@ export default function CreateAnnouncement() {
|
||||
<Text style={[Styles.textDefaultSemiBold]}>{item.name}</Text>
|
||||
{
|
||||
item.Division.map((division: any, i: any) => (
|
||||
<View key={i} style={[Styles.rowItemsCenter]}>
|
||||
<View key={i} style={[Styles.rowItemsCenter, Styles.w90]}>
|
||||
<Entypo name="dot-single" size={24} color="black" />
|
||||
<Text style={[Styles.textDefault]}>{division.name}</Text>
|
||||
<Text style={[Styles.textDefault]} numberOfLines={1} ellipsizeMode='tail'>{division.name}</Text>
|
||||
</View>
|
||||
))
|
||||
}
|
||||
|
||||
@@ -193,9 +193,9 @@ export default function EditAnnouncement() {
|
||||
<Text style={[Styles.textDefaultSemiBold]}>{item.name}</Text>
|
||||
{
|
||||
item.Division.map((division: any, i: any) => (
|
||||
<View key={i} style={[Styles.rowItemsCenter]}>
|
||||
<View key={i} style={[Styles.rowItemsCenter, Styles.w90]}>
|
||||
<Entypo name="dot-single" size={24} color="black" />
|
||||
<Text style={[Styles.textDefault]}>{division.name}</Text>
|
||||
<Text style={[Styles.textDefault]} numberOfLines={1} ellipsizeMode='tail'>{division.name}</Text>
|
||||
</View>
|
||||
))
|
||||
}
|
||||
|
||||
@@ -87,7 +87,7 @@ export default function Announcement() {
|
||||
<View>
|
||||
<InputSearch onChange={setSearch} />
|
||||
</View>
|
||||
<View style={[{ flex: 2 }, Styles.mb50]}>
|
||||
<View style={[{ flex: 2 }]}>
|
||||
{
|
||||
loading ?
|
||||
arrSkeleton.map((item, index) => {
|
||||
|
||||
@@ -2,6 +2,7 @@ import ButtonBackHeader from "@/components/buttonBackHeader";
|
||||
import ButtonSaveHeader from "@/components/buttonSaveHeader";
|
||||
import { InputForm } from "@/components/inputForm";
|
||||
import Text from "@/components/Text";
|
||||
import { ConstEnv } from "@/constants/ConstEnv";
|
||||
import Styles from "@/constants/Styles";
|
||||
import { apiEditBanner, apiGetBanner, apiGetBannerOne } from "@/lib/api";
|
||||
import { setEntities } from "@/lib/bannerSlice";
|
||||
@@ -50,7 +51,7 @@ export default function EditBanner() {
|
||||
const hasil = await decryptToken(String(token?.current));
|
||||
const data = await apiGetBannerOne({ user: hasil, id });
|
||||
setSelectedImage({
|
||||
uri: `https://wibu-storage.wibudev.com/api/files/${data.data.image}`,
|
||||
uri: `${ConstEnv.url_storage}/files/${data.data.image}`,
|
||||
});
|
||||
setTitle(data.data.title);
|
||||
};
|
||||
|
||||
@@ -5,6 +5,7 @@ import ButtonBackHeader from "@/components/buttonBackHeader"
|
||||
import DrawerBottom from "@/components/drawerBottom"
|
||||
import MenuItemRow from "@/components/menuItemRow"
|
||||
import ModalLoading from "@/components/modalLoading"
|
||||
import { ConstEnv } from "@/constants/ConstEnv"
|
||||
import Styles from "@/constants/Styles"
|
||||
import { apiDeleteBanner, apiGetBanner } from "@/lib/api"
|
||||
import { setEntities } from "@/lib/bannerSlice"
|
||||
@@ -20,7 +21,6 @@ import * as mime from 'react-native-mime-types'
|
||||
import Toast from "react-native-toast-message"
|
||||
import { useDispatch, useSelector } from "react-redux"
|
||||
|
||||
|
||||
type Props = {
|
||||
id: string
|
||||
title: string
|
||||
@@ -71,7 +71,7 @@ export default function BannerList() {
|
||||
const openFile = () => {
|
||||
setModal(false)
|
||||
setLoadingOpen(true)
|
||||
let remoteUrl = 'https://wibu-storage.wibudev.com/api/files/' + selectFile?.image;
|
||||
let remoteUrl = ConstEnv.url_storage + '/files/' + selectFile?.image;
|
||||
const fileName = selectFile?.title + '.' + selectFile?.extension;
|
||||
let localPath = `${FileSystem.documentDirectory}/${fileName}`;
|
||||
const mimeType = mime.lookup(fileName)
|
||||
@@ -112,7 +112,7 @@ export default function BannerList() {
|
||||
}}
|
||||
/>
|
||||
<ModalLoading isVisible={loadingOpen} setVisible={setLoadingOpen} />
|
||||
<ScrollView
|
||||
<ScrollView
|
||||
refreshControl={
|
||||
<RefreshControl
|
||||
refreshing={refreshing}
|
||||
@@ -133,7 +133,7 @@ export default function BannerList() {
|
||||
borderType="all"
|
||||
icon={
|
||||
<Image
|
||||
source={{ uri: `https://wibu-storage.wibudev.com/api/files/${index.image}` }}
|
||||
source={{ uri: `${ConstEnv.url_storage}/files/${index.image}` }}
|
||||
style={[Styles.imgListBanner]}
|
||||
/>
|
||||
}
|
||||
|
||||
@@ -8,16 +8,18 @@ import Skeleton from "@/components/skeleton";
|
||||
import SkeletonContent from "@/components/skeletonContent";
|
||||
import Text from '@/components/Text';
|
||||
import { ColorsStatus } from "@/constants/ColorsStatus";
|
||||
import { ConstEnv } from "@/constants/ConstEnv";
|
||||
import Styles from "@/constants/Styles";
|
||||
import { apiGetDiscussionGeneralOne, apiSendDiscussionGeneralCommentar } from "@/lib/api";
|
||||
import { getDB } from "@/lib/firebaseDatabase";
|
||||
import { useAuthSession } from "@/providers/AuthProvider";
|
||||
import { Ionicons, MaterialIcons } from "@expo/vector-icons";
|
||||
import { firebase } from '@react-native-firebase/database';
|
||||
import { ref } from '@react-native-firebase/database';
|
||||
import { useHeaderHeight } from '@react-navigation/elements';
|
||||
import { router, Stack, useLocalSearchParams } from "expo-router";
|
||||
import { useEffect, useState } from "react";
|
||||
import { KeyboardAvoidingView, Platform, Pressable, ScrollView, View } from "react-native";
|
||||
import { useSelector } from "react-redux";
|
||||
import { useHeaderHeight } from '@react-navigation/elements';
|
||||
|
||||
type Props = {
|
||||
id: string
|
||||
@@ -49,7 +51,7 @@ export default function DetailDiscussionGeneral() {
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [loadingKomentar, setLoadingKomentar] = useState(true)
|
||||
const arrSkeleton = Array.from({ length: 3 }, (_, index) => index)
|
||||
const reference = firebase.app().database('https://mobile-darmasaba-default-rtdb.asia-southeast1.firebasedatabase.app').ref(`/discussion-general/${id}`);
|
||||
const reference = ref(getDB(), `/discussion-general/${id}`);
|
||||
const headerHeight = useHeaderHeight();
|
||||
|
||||
useEffect(() => {
|
||||
@@ -145,7 +147,6 @@ export default function DetailDiscussionGeneral() {
|
||||
:
|
||||
<BorderBottomItem
|
||||
descEllipsize={false}
|
||||
width={55}
|
||||
borderType="bottom"
|
||||
icon={
|
||||
<View style={[Styles.iconContent, ColorsStatus.lightGreen]}>
|
||||
@@ -182,10 +183,9 @@ export default function DetailDiscussionGeneral() {
|
||||
return (
|
||||
<BorderBottomItem
|
||||
key={i}
|
||||
width={55}
|
||||
borderType="bottom"
|
||||
icon={
|
||||
<ImageUser src={`https://wibu-storage.wibudev.com/api/files/${item.img}`} size="xs" />
|
||||
<ImageUser src={`${ConstEnv.url_storage}/files/${item.img}`} size="xs" />
|
||||
}
|
||||
title={item.username}
|
||||
rightTopInfo={item.createdAt}
|
||||
|
||||
@@ -4,6 +4,7 @@ import ImageUser from "@/components/imageNew";
|
||||
import ImageWithLabel from "@/components/imageWithLabel";
|
||||
import InputSearch from "@/components/inputSearch";
|
||||
import Text from '@/components/Text';
|
||||
import { ConstEnv } from "@/constants/ConstEnv";
|
||||
import Styles from "@/constants/Styles";
|
||||
import { apiAddMemberDiscussionGeneral, apiGetDiscussionGeneralOne, apiGetUser } from "@/lib/api";
|
||||
import { setUpdateDiscussionGeneralDetail } from "@/lib/discussionGeneralDetail";
|
||||
@@ -121,7 +122,7 @@ export default function AddMemberDiscussionDetail() {
|
||||
<ImageWithLabel
|
||||
key={index}
|
||||
label={item.name}
|
||||
src={`https://wibu-storage.wibudev.com/api/files/${item.img}`}
|
||||
src={`${ConstEnv.url_storage}/files/${item.img}`}
|
||||
onClick={() => onChoose(item.idUser, item.name, item.img)}
|
||||
/>
|
||||
))
|
||||
@@ -147,7 +148,7 @@ export default function AddMemberDiscussionDetail() {
|
||||
}}
|
||||
>
|
||||
<View style={[Styles.rowItemsCenter]}>
|
||||
<ImageUser src={`https://wibu-storage.wibudev.com/api/files/${item.img}`} border />
|
||||
<ImageUser src={`${ConstEnv.url_storage}/files/${item.img}`} border />
|
||||
<View style={[Styles.ml10]}>
|
||||
<Text style={[Styles.textDefault]}>{item.name}</Text>
|
||||
{
|
||||
@@ -156,7 +157,7 @@ export default function AddMemberDiscussionDetail() {
|
||||
</View>
|
||||
</View>
|
||||
{
|
||||
selectMember.some((i: any) => i.idUser == item.id) && <AntDesign name="check" size={20} color={'black'}/>
|
||||
selectMember.some((i: any) => i.idUser == item.id) && <AntDesign name="check" size={20} color={'black'} />
|
||||
}
|
||||
</Pressable>
|
||||
)
|
||||
|
||||
@@ -7,6 +7,7 @@ import { InputForm } from "@/components/inputForm";
|
||||
import ModalSelect from "@/components/modalSelect";
|
||||
import SelectForm from "@/components/selectForm";
|
||||
import Text from '@/components/Text';
|
||||
import { ConstEnv } from "@/constants/ConstEnv";
|
||||
import Styles from "@/constants/Styles";
|
||||
import { apiCreateDiscussionGeneral } from "@/lib/api";
|
||||
import { setUpdateDiscussionGeneralDetail } from "@/lib/discussionGeneralDetail";
|
||||
@@ -215,7 +216,7 @@ export default function CreateDiscussionGeneral() {
|
||||
key={index}
|
||||
borderType="bottom"
|
||||
icon={
|
||||
<ImageUser src={`https://wibu-storage.wibudev.com/api/files/${item.img}`} size="sm" />
|
||||
<ImageUser src={`${ConstEnv.url_storage}/files/${item.img}`} size="sm" />
|
||||
}
|
||||
title={item.name}
|
||||
/>
|
||||
|
||||
@@ -7,6 +7,7 @@ import MenuItemRow from "@/components/menuItemRow";
|
||||
import SkeletonTwoItem from "@/components/skeletonTwoItem";
|
||||
import Text from '@/components/Text';
|
||||
import { ColorsStatus } from "@/constants/ColorsStatus";
|
||||
import { ConstEnv } from "@/constants/ConstEnv";
|
||||
import Styles from "@/constants/Styles";
|
||||
import { apiDeleteMemberDiscussionGeneral, apiGetDiscussionGeneralOne } from "@/lib/api";
|
||||
import { useAuthSession } from "@/providers/AuthProvider";
|
||||
@@ -109,7 +110,7 @@ export default function MemberDiscussionDetail() {
|
||||
key={index}
|
||||
borderType="bottom"
|
||||
icon={
|
||||
<ImageUser src={`https://wibu-storage.wibudev.com/api/files/${item.img}`} size="sm" />
|
||||
<ImageUser src={`${ConstEnv.url_storage}/files/${item.img}`} size="sm" />
|
||||
}
|
||||
title={item.name}
|
||||
onPress={() => {
|
||||
|
||||
@@ -4,6 +4,7 @@ import ImageUser from "@/components/imageNew";
|
||||
import ImageWithLabel from "@/components/imageWithLabel";
|
||||
import InputSearch from "@/components/inputSearch";
|
||||
import Text from "@/components/Text";
|
||||
import { ConstEnv } from "@/constants/ConstEnv";
|
||||
import Styles from "@/constants/Styles";
|
||||
import { apiAddMemberCalendar, apiGetCalendarOne, apiGetDivisionMember } from "@/lib/api";
|
||||
import { setUpdateCalendar } from "@/lib/calendarUpdate";
|
||||
@@ -128,7 +129,7 @@ export default function AddMemberCalendarEvent() {
|
||||
<ImageWithLabel
|
||||
key={index}
|
||||
label={item.name}
|
||||
src={`https://wibu-storage.wibudev.com/api/files/${item.img}`}
|
||||
src={`${ConstEnv.url_storage}/files/${item.img}`}
|
||||
onClick={() => onChoose(item.idUser, item.name, item.img)}
|
||||
/>
|
||||
))
|
||||
@@ -154,7 +155,7 @@ export default function AddMemberCalendarEvent() {
|
||||
}}
|
||||
>
|
||||
<View style={[Styles.rowItemsCenter]}>
|
||||
<ImageUser src={`https://wibu-storage.wibudev.com/api/files/${item.img}`} border />
|
||||
<ImageUser src={`${ConstEnv.url_storage}/files/${item.img}`} border />
|
||||
<View style={[Styles.ml10, { width: '80%' }]}>
|
||||
<Text numberOfLines={1} ellipsizeMode="tail" style={[Styles.textDefault]}>{item.name}</Text>
|
||||
{
|
||||
@@ -163,7 +164,7 @@ export default function AddMemberCalendarEvent() {
|
||||
</View>
|
||||
</View>
|
||||
{
|
||||
selectMember.some((i: any) => i.idUser == item.id) && <AntDesign name="check" size={20} color={'black'}/>
|
||||
selectMember.some((i: any) => i.idUser == item.id) && <AntDesign name="check" size={20} color={'black'} />
|
||||
}
|
||||
</Pressable>
|
||||
)
|
||||
|
||||
@@ -7,6 +7,7 @@ import ImageUser from "@/components/imageNew"
|
||||
import MenuItemRow from "@/components/menuItemRow"
|
||||
import Skeleton from "@/components/skeleton"
|
||||
import Text from "@/components/Text"
|
||||
import { ConstEnv } from "@/constants/ConstEnv"
|
||||
import Styles from "@/constants/Styles"
|
||||
import { apiDeleteCalendarMember, apiGetCalendarOne, apiGetDivisionOneFeature } from "@/lib/api"
|
||||
import { setUpdateCalendar } from "@/lib/calendarUpdate"
|
||||
@@ -70,7 +71,7 @@ export default function DetailEventCalendar() {
|
||||
}
|
||||
}
|
||||
|
||||
async function handleLoad(loading:boolean) {
|
||||
async function handleLoad(loading: boolean) {
|
||||
try {
|
||||
setLoading(loading)
|
||||
const hasil = await decryptToken(String(token?.current));
|
||||
@@ -241,7 +242,7 @@ export default function DetailEventCalendar() {
|
||||
<BorderBottomItem
|
||||
key={index}
|
||||
borderType="bottom"
|
||||
icon={<ImageUser src={`https://wibu-storage.wibudev.com/api/files/${item.img}`} />}
|
||||
icon={<ImageUser src={`${ConstEnv.url_storage}/files/${item.img}`} />}
|
||||
title={item.name}
|
||||
subtitle={item.email}
|
||||
onPress={() => {
|
||||
|
||||
@@ -4,6 +4,7 @@ import ImageUser from "@/components/imageNew";
|
||||
import ImageWithLabel from "@/components/imageWithLabel";
|
||||
import InputSearch from "@/components/inputSearch";
|
||||
import Text from "@/components/Text";
|
||||
import { ConstEnv } from "@/constants/ConstEnv";
|
||||
import Styles from "@/constants/Styles";
|
||||
import { apiCreateCalendar, apiGetDivisionMember } from "@/lib/api";
|
||||
import { setFormCreateCalendar } from "@/lib/calendarCreate";
|
||||
@@ -117,7 +118,7 @@ export default function CreateCalendarAddMember() {
|
||||
<ImageWithLabel
|
||||
key={index}
|
||||
label={item.name}
|
||||
src={`https://wibu-storage.wibudev.com/api/files/${item.img}`}
|
||||
src={`${ConstEnv.url_storage}/files/${item.img}`}
|
||||
onClick={() => onChoose(item.idUser, item.name, item.img)}
|
||||
/>
|
||||
))
|
||||
@@ -140,13 +141,13 @@ export default function CreateCalendarAddMember() {
|
||||
onPress={() => { onChoose(item.idUser, item.name, item.img) }}
|
||||
>
|
||||
<View style={[Styles.rowItemsCenter, Styles.w70]}>
|
||||
<ImageUser src={`https://wibu-storage.wibudev.com/api/files/${item.img}`} border />
|
||||
<ImageUser src={`${ConstEnv.url_storage}/files/${item.img}`} border />
|
||||
<View style={[Styles.ml10]}>
|
||||
<Text style={[Styles.textDefault]} numberOfLines={1} ellipsizeMode="tail">{item.name}</Text>
|
||||
</View>
|
||||
</View>
|
||||
{
|
||||
selectMember.some((i: any) => i.idUser == item.idUser) && <AntDesign name="check" size={20} color={'black'}/>
|
||||
selectMember.some((i: any) => i.idUser == item.idUser) && <AntDesign name="check" size={20} color={'black'} />
|
||||
}
|
||||
</Pressable>
|
||||
)
|
||||
|
||||
@@ -8,7 +8,6 @@ import Styles from "@/constants/Styles";
|
||||
import { apiGetCalendarByDateDivision, apiGetIndicatorCalendar } from "@/lib/api";
|
||||
import { useAuthSession } from "@/providers/AuthProvider";
|
||||
import { Feather } from "@expo/vector-icons";
|
||||
import dayjs from "dayjs";
|
||||
import { router, Stack, useLocalSearchParams } from "expo-router";
|
||||
import 'intl';
|
||||
import 'intl/locale-data/jsonp/id';
|
||||
@@ -53,7 +52,7 @@ export default function CalendarDivision() {
|
||||
const hasil = await decryptToken(String(token?.current));
|
||||
const response = await apiGetCalendarByDateDivision({
|
||||
user: hasil,
|
||||
date: dayjs(selected).format("YYYY-MM-DD"),
|
||||
date: moment(selected).format("YYYY-MM-DD"),
|
||||
division: id,
|
||||
});
|
||||
setData(response.data);
|
||||
@@ -71,7 +70,7 @@ export default function CalendarDivision() {
|
||||
const hasil = await decryptToken(String(token?.current));
|
||||
const response = await apiGetIndicatorCalendar({
|
||||
user: hasil,
|
||||
date: dayjs(newDate).format("YYYY-MM-DD"),
|
||||
date: moment(newDate).format("YYYY-MM-DD"),
|
||||
division: id,
|
||||
});
|
||||
setDataIndicator(response.data);
|
||||
@@ -113,6 +112,7 @@ export default function CalendarDivision() {
|
||||
text={day.text}
|
||||
isSelected={day.isSelected}
|
||||
isSign={sign}
|
||||
onPress={() => setSelected(new Date(today))}
|
||||
/>
|
||||
);
|
||||
},
|
||||
@@ -156,7 +156,6 @@ export default function CalendarDivision() {
|
||||
mode="single"
|
||||
date={selected}
|
||||
month={month}
|
||||
onChange={({ date }) => setSelected(date)}
|
||||
onMonthChange={(month) => setMonth(month)}
|
||||
styles={{
|
||||
selected: Styles.selectedDate,
|
||||
|
||||
@@ -7,20 +7,22 @@ import LabelStatus from "@/components/labelStatus";
|
||||
import Skeleton from "@/components/skeleton";
|
||||
import SkeletonContent from "@/components/skeletonContent";
|
||||
import Text from "@/components/Text";
|
||||
import { ConstEnv } from "@/constants/ConstEnv";
|
||||
import Styles from "@/constants/Styles";
|
||||
import {
|
||||
apiGetDiscussionOne,
|
||||
apiGetDivisionOneFeature,
|
||||
apiSendDiscussionCommentar,
|
||||
} from "@/lib/api";
|
||||
import { getDB } from "@/lib/firebaseDatabase";
|
||||
import { useAuthSession } from "@/providers/AuthProvider";
|
||||
import { Ionicons, MaterialIcons } from "@expo/vector-icons";
|
||||
import { firebase } from "@react-native-firebase/database";
|
||||
import { ref } from "@react-native-firebase/database";
|
||||
import { useHeaderHeight } from '@react-navigation/elements';
|
||||
import { router, Stack, useLocalSearchParams } from "expo-router";
|
||||
import { useEffect, useState } from "react";
|
||||
import { KeyboardAvoidingView, Platform, Pressable, RefreshControl, ScrollView, View } from "react-native";
|
||||
import { useSelector } from "react-redux";
|
||||
import { useHeaderHeight } from '@react-navigation/elements';
|
||||
|
||||
type Props = {
|
||||
id: string;
|
||||
@@ -58,7 +60,7 @@ export default function DiscussionDetail() {
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [loadingKomentar, setLoadingKomentar] = useState(true)
|
||||
const arrSkeleton = Array.from({ length: 3 })
|
||||
const reference = firebase.app().database('https://mobile-darmasaba-default-rtdb.asia-southeast1.firebasedatabase.app').ref(`/discussion-division/${detail}`);
|
||||
const reference = ref(getDB(), `/discussion-division/${detail}`);
|
||||
const [refreshing, setRefreshing] = useState(false)
|
||||
const headerHeight = useHeaderHeight();
|
||||
|
||||
@@ -216,11 +218,10 @@ export default function DiscussionDetail() {
|
||||
:
|
||||
<BorderBottomItem
|
||||
descEllipsize={false}
|
||||
width={55}
|
||||
borderType="bottom"
|
||||
icon={
|
||||
<ImageUser
|
||||
src={`https://wibu-storage.wibudev.com/api/files/${data?.user_img}`}
|
||||
src={`${ConstEnv.url_storage}/files/${data?.user_img}`}
|
||||
size="sm"
|
||||
/>
|
||||
}
|
||||
@@ -264,11 +265,10 @@ export default function DiscussionDetail() {
|
||||
dataComment.map((item, index) => (
|
||||
<BorderBottomItem
|
||||
key={index}
|
||||
width={55}
|
||||
borderType="bottom"
|
||||
icon={
|
||||
<ImageUser
|
||||
src={`https://wibu-storage.wibudev.com/api/files/${item.img}`}
|
||||
src={`${ConstEnv.url_storage}/files/${item.img}`}
|
||||
size="xs"
|
||||
/>
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import InputSearch from "@/components/inputSearch";
|
||||
import LabelStatus from "@/components/labelStatus";
|
||||
import SkeletonContent from "@/components/skeletonContent";
|
||||
import Text from "@/components/Text";
|
||||
import { ConstEnv } from "@/constants/ConstEnv";
|
||||
import Styles from "@/constants/Styles";
|
||||
import { apiGetDiscussion } from "@/lib/api";
|
||||
import { useAuthSession } from "@/providers/AuthProvider";
|
||||
@@ -138,11 +139,10 @@ export default function DiscussionDivision() {
|
||||
return (
|
||||
<BorderBottomItem
|
||||
key={index}
|
||||
width={55}
|
||||
onPress={() => { router.push(`./discussion/${item.id}`) }}
|
||||
borderType="bottom"
|
||||
icon={
|
||||
<ImageUser src={`https://wibu-storage.wibudev.com/api/files/${item.img}`} size="sm" />
|
||||
<ImageUser src={`${ConstEnv.url_storage}/files/${item.img}`} size="sm" />
|
||||
}
|
||||
title={item.user_name}
|
||||
subtitle={
|
||||
@@ -171,34 +171,8 @@ export default function DiscussionDivision() {
|
||||
/>
|
||||
}
|
||||
/>
|
||||
// data.map((item, index) => (
|
||||
// <BorderBottomItem
|
||||
// key={index}
|
||||
// width={55}
|
||||
// onPress={() => { router.push(`./discussion/${item.id}`) }}
|
||||
// borderType="bottom"
|
||||
// icon={
|
||||
// <ImageUser src={`https://wibu-storage.wibudev.com/api/files/${item.img}`} size="sm" />
|
||||
// }
|
||||
// title={item.user_name}
|
||||
// subtitle={
|
||||
// active == "true" ? item.status == 1 ? <LabelStatus category='success' text='BUKA' size="small" /> : <LabelStatus category='error' text='TUTUP' size="small" /> : <></>
|
||||
// }
|
||||
// rightTopInfo={item.createdAt}
|
||||
// desc={item.desc}
|
||||
// leftBottomInfo={
|
||||
// <View style={[Styles.rowItemsCenter]}>
|
||||
// <Ionicons name="chatbox-ellipses-outline" size={18} color="grey" style={Styles.mr05} />
|
||||
// <Text style={[Styles.textInformation, Styles.cGray, Styles.mb05]}>Diskusikan</Text>
|
||||
// </View>
|
||||
// }
|
||||
// rightBottomInfo={item.total_komentar + ' Komentar'}
|
||||
// />
|
||||
// ))
|
||||
:
|
||||
(
|
||||
<Text style={[Styles.textDefault, Styles.cGray, Styles.mv10, { textAlign: "center" }]}>Tidak ada diskusi</Text>
|
||||
)
|
||||
(<Text style={[Styles.textDefault, Styles.cGray, Styles.mv10, { textAlign: "center" }]}>Tidak ada diskusi</Text>)
|
||||
}
|
||||
</View>
|
||||
</View>
|
||||
|
||||
@@ -13,6 +13,7 @@ import ModalSelectMultiple from "@/components/modalSelectMultiple";
|
||||
import Skeleton from "@/components/skeleton";
|
||||
import Text from "@/components/Text";
|
||||
import { ColorsStatus } from "@/constants/ColorsStatus";
|
||||
import { ConstEnv } from "@/constants/ConstEnv";
|
||||
import Styles from "@/constants/Styles";
|
||||
import {
|
||||
apiDocumentDelete,
|
||||
@@ -268,7 +269,7 @@ export default function DocumentDivision() {
|
||||
|
||||
const openFile = (item: Props) => {
|
||||
if (Platform.OS == 'android') setLoadingOpen(true)
|
||||
let remoteUrl = 'https://wibu-storage.wibudev.com/api/files/' + item.idStorage;
|
||||
let remoteUrl = ConstEnv.url_storage + '/files/' + item.idStorage;
|
||||
const fileName = item.name + '.' + item.extension;
|
||||
let localPath = `${FileSystem.documentDirectory}/${fileName}`;
|
||||
const mimeType = mime.lookup(fileName)
|
||||
@@ -440,20 +441,6 @@ export default function DocumentDivision() {
|
||||
{(selectedFiles.length > 0 || dariSelectAll) && (
|
||||
<View style={[ColorsStatus.primary, Styles.bottomMenuSelectDocument]}>
|
||||
<View style={[Styles.rowItemsCenter, { justifyContent: "center" }]}>
|
||||
{/* <MenuItemRow
|
||||
icon={
|
||||
<MaterialCommunityIcons
|
||||
name="download-outline"
|
||||
color="white"
|
||||
size={25}
|
||||
/>
|
||||
}
|
||||
title="Unduh"
|
||||
onPress={() => { }}
|
||||
column="many"
|
||||
color="white"
|
||||
disabled={selectedFiles.length == 0 || !copyAllowed}
|
||||
/> */}
|
||||
<MenuItemRow
|
||||
icon={
|
||||
<MaterialCommunityIcons
|
||||
|
||||
@@ -4,6 +4,7 @@ import ImageUser from "@/components/imageNew";
|
||||
import ImageWithLabel from "@/components/imageWithLabel";
|
||||
import InputSearch from "@/components/inputSearch";
|
||||
import Text from "@/components/Text";
|
||||
import { ConstEnv } from "@/constants/ConstEnv";
|
||||
import Styles from "@/constants/Styles";
|
||||
import { apiAddMemberTask, apiGetDivisionMember, apiGetTaskOne } from "@/lib/api";
|
||||
import { setUpdateTask } from "@/lib/taskUpdate";
|
||||
@@ -123,7 +124,7 @@ export default function AddMemberTask() {
|
||||
<ImageWithLabel
|
||||
key={index}
|
||||
label={item.name}
|
||||
src={`https://wibu-storage.wibudev.com/api/files/${item.img}`}
|
||||
src={`${ConstEnv.url_storage}/files/${item.img}`}
|
||||
onClick={() => onChoose(item.idUser, item.name, item.img)}
|
||||
/>
|
||||
))
|
||||
@@ -149,7 +150,7 @@ export default function AddMemberTask() {
|
||||
}}
|
||||
>
|
||||
<View style={[Styles.rowItemsCenter, Styles.w80]}>
|
||||
<ImageUser src={`https://wibu-storage.wibudev.com/api/files/${item.img}`} border />
|
||||
<ImageUser src={`${ConstEnv.url_storage}/files/${item.img}`} border />
|
||||
<View style={[Styles.ml10]}>
|
||||
<Text style={[Styles.textDefault]} numberOfLines={1}>{item.name}</Text>
|
||||
{
|
||||
@@ -158,7 +159,7 @@ export default function AddMemberTask() {
|
||||
</View>
|
||||
</View>
|
||||
{
|
||||
selectMember.some((i: any) => i.idUser == item.idUser) && <AntDesign name="check" size={20} color={'black'}/>
|
||||
selectMember.some((i: any) => i.idUser == item.idUser) && <AntDesign name="check" size={20} color={'black'} />
|
||||
}
|
||||
</Pressable>
|
||||
)
|
||||
|
||||
@@ -1,25 +1,28 @@
|
||||
import ButtonBackHeader from "@/components/buttonBackHeader";
|
||||
import ButtonSaveHeader from "@/components/buttonSaveHeader";
|
||||
import { InputForm } from "@/components/inputForm";
|
||||
import ModalAddDetailTugasTask from "@/components/task/modalAddDetailTugasTask";
|
||||
import Text from "@/components/Text";
|
||||
import Styles from "@/constants/Styles";
|
||||
import { apiCreateTaskTugas } from "@/lib/api";
|
||||
import { formatDateOnly } from "@/lib/fun_formatDateOnly";
|
||||
import { getDatesInRange } from "@/lib/fun_getDatesInRange";
|
||||
import { setUpdateTask } from "@/lib/taskUpdate";
|
||||
import { useAuthSession } from "@/providers/AuthProvider";
|
||||
import dayjs from "dayjs";
|
||||
import { useHeaderHeight } from '@react-navigation/elements';
|
||||
import { router, Stack, useLocalSearchParams } from "expo-router";
|
||||
import 'intl';
|
||||
import 'intl/locale-data/jsonp/id';
|
||||
import moment from "moment";
|
||||
import { useEffect, useState } from "react";
|
||||
import {
|
||||
KeyboardAvoidingView, Platform, SafeAreaView,
|
||||
KeyboardAvoidingView, Platform, Pressable, SafeAreaView,
|
||||
ScrollView,
|
||||
View
|
||||
} from "react-native";
|
||||
import Toast from "react-native-toast-message";
|
||||
import DateTimePicker, { DateType } from "react-native-ui-datepicker";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { useHeaderHeight } from '@react-navigation/elements';
|
||||
|
||||
export default function TaskDivisionAddTask() {
|
||||
const { token, decryptToken } = useAuthSession();
|
||||
@@ -38,12 +41,13 @@ export default function TaskDivisionAddTask() {
|
||||
endDate: false,
|
||||
title: false,
|
||||
});
|
||||
const [title, setTitle] = useState("");
|
||||
const [title, setTitle] = useState("")
|
||||
const [dataDetail, setDataDetail] = useState<any>([])
|
||||
const [modalDetail, setModalDetail] = useState(false)
|
||||
const [dsbButton, setDsbButton] = useState(true)
|
||||
|
||||
const from = range.startDate
|
||||
? dayjs(range.startDate).format("DD-MM-YYYY")
|
||||
: "";
|
||||
const to = range.endDate ? dayjs(range.endDate).format("DD-MM-YYYY") : "";
|
||||
const from = formatDateOnly(range.startDate);
|
||||
const to = formatDateOnly(range.endDate);
|
||||
|
||||
function checkAll() {
|
||||
if (
|
||||
@@ -72,21 +76,49 @@ export default function TaskDivisionAddTask() {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function checkButton() {
|
||||
if (range.startDate == null || range.endDate == null || range.startDate == undefined || range.endDate == undefined) {
|
||||
setDsbButton(true)
|
||||
setDataDetail([])
|
||||
} else {
|
||||
setDsbButton(false)
|
||||
const awal = formatDateOnly(range.startDate, "YYYY-MM-DD")
|
||||
const akhir = formatDateOnly(range.endDate, "YYYY-MM-DD")
|
||||
const datanya = getDatesInRange(awal, akhir)
|
||||
setDataDetail(datanya.map((item: any) => ({
|
||||
date: item,
|
||||
timeStart: null,
|
||||
timeEnd: null,
|
||||
})))
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
checkAll();
|
||||
}, [from, to, title, error]);
|
||||
|
||||
useEffect(() => {
|
||||
checkButton()
|
||||
}, [range])
|
||||
|
||||
async function handleCreate() {
|
||||
try {
|
||||
setLoading(true)
|
||||
const dataDetailFix = dataDetail.map((item: any) => ({
|
||||
date: moment(item.date, "DD-MM-YYYY").format("YYYY-MM-DD"),
|
||||
timeStart: item.timeStart,
|
||||
timeEnd: item.timeEnd,
|
||||
}))
|
||||
const hasil = await decryptToken(String(token?.current));
|
||||
const response = await apiCreateTaskTugas({
|
||||
data: {
|
||||
title,
|
||||
dateStart: dayjs(range.startDate).format("YYYY-MM-DD"),
|
||||
dateEnd: dayjs(range.endDate).format("YYYY-MM-DD"),
|
||||
dateStart: formatDateOnly(range.startDate, "YYYY-MM-DD"),
|
||||
dateEnd: formatDateOnly(range.endDate, "YYYY-MM-DD"),
|
||||
user: hasil,
|
||||
idDivision: id,
|
||||
dataDetail: dataDetailFix,
|
||||
},
|
||||
id: detail,
|
||||
});
|
||||
@@ -177,6 +209,13 @@ export default function TaskDivisionAddTask() {
|
||||
{
|
||||
(error.endDate || error.startDate) && <Text style={[Styles.textInformation, Styles.cError, Styles.mt05]}>Tanggal tidak boleh kosong</Text>
|
||||
}
|
||||
<Pressable
|
||||
style={[Styles.btnTab, Styles.btnLainnya, dsbButton && Styles.btnDisabled]}
|
||||
disabled={dsbButton}
|
||||
onPress={() => { setModalDetail(true) }}
|
||||
>
|
||||
<Text style={[dsbButton ? Styles.cGray : Styles.cWhite]}>Detail</Text>
|
||||
</Pressable>
|
||||
</View>
|
||||
<InputForm
|
||||
label="Judul Tugas"
|
||||
@@ -194,7 +233,14 @@ export default function TaskDivisionAddTask() {
|
||||
</View>
|
||||
</ScrollView>
|
||||
</KeyboardAvoidingView>
|
||||
|
||||
<ModalAddDetailTugasTask
|
||||
isVisible={modalDetail}
|
||||
setVisible={setModalDetail}
|
||||
dataTanggal={dataDetail}
|
||||
onSubmit={(data) => {
|
||||
setDataDetail(data)
|
||||
}}
|
||||
/>
|
||||
</SafeAreaView>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3,7 +3,9 @@ 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 SectionReportTask from "@/components/task/sectionReportTask";
|
||||
import SectionTanggalTugasTask from "@/components/task/sectionTanggalTugasTask";
|
||||
import Styles from "@/constants/Styles";
|
||||
import { apiGetTaskOne } from "@/lib/api";
|
||||
@@ -88,9 +90,11 @@ export default function DetailTaskDivision() {
|
||||
data?.reason != null && data?.reason != "" && <SectionCancel text={data?.reason} />
|
||||
}
|
||||
<SectionProgress text={`Kemajuan Kegiatan ${progress}%`} progress={progress} />
|
||||
<SectionTanggalTugasTask refreshing={refreshing}/>
|
||||
<SectionFileTask refreshing={refreshing}/>
|
||||
<SectionMemberTask refreshing={refreshing}/>
|
||||
<SectionReportTask refreshing={refreshing} />
|
||||
<SectionTanggalTugasTask refreshing={refreshing} />
|
||||
<SectionFileTask refreshing={refreshing} />
|
||||
<SectionLinkTask refreshing={refreshing} />
|
||||
<SectionMemberTask refreshing={refreshing} />
|
||||
</View>
|
||||
</ScrollView>
|
||||
</SafeAreaView>
|
||||
|
||||
@@ -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 (
|
||||
<SafeAreaView>
|
||||
<Stack.Screen
|
||||
options={{
|
||||
headerLeft: () => (
|
||||
<ButtonBackHeader
|
||||
onPress={() => {
|
||||
router.back();
|
||||
}}
|
||||
/>
|
||||
),
|
||||
headerTitle: "Laporan Kegiatan",
|
||||
headerTitleAlign: "center",
|
||||
headerRight: () => (
|
||||
<ButtonSaveHeader
|
||||
category="update"
|
||||
disable={disable || loading}
|
||||
onPress={() => { handleUpdate() }}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<ScrollView>
|
||||
<View style={[Styles.p15, Styles.mb100]}>
|
||||
<InputForm
|
||||
label="Laporan Kegiatan"
|
||||
type="default"
|
||||
placeholder="Laporan Kegiatan"
|
||||
required
|
||||
bg="white"
|
||||
value={laporan}
|
||||
onChange={(val) => { onValidation(val) }}
|
||||
error={error}
|
||||
errorText="Laporan kegiatan harus diisi"
|
||||
multiline
|
||||
/>
|
||||
</View>
|
||||
</ScrollView>
|
||||
</SafeAreaView>
|
||||
);
|
||||
}
|
||||
@@ -9,6 +9,7 @@ import MenuItemRow from "@/components/menuItemRow";
|
||||
import ModalSelect from "@/components/modalSelect";
|
||||
import SectionListAddTask from "@/components/project/sectionListAddTask";
|
||||
import Text from "@/components/Text";
|
||||
import { ConstEnv } from "@/constants/ConstEnv";
|
||||
import Styles from "@/constants/Styles";
|
||||
import { apiCreateTask } from "@/lib/api";
|
||||
import { setMemberChoose } from "@/lib/memberChoose";
|
||||
@@ -189,7 +190,7 @@ export default function CreateTaskDivision() {
|
||||
borderType="bottom"
|
||||
icon={
|
||||
<ImageUser
|
||||
src={`https://wibu-storage.wibudev.com/api/files/${item.img}`}
|
||||
src={`${ConstEnv.url_storage}/files/${item.img}`}
|
||||
size="sm"
|
||||
/>
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import ImageUser from "@/components/imageNew";
|
||||
import ImageWithLabel from "@/components/imageWithLabel";
|
||||
import InputSearch from "@/components/inputSearch";
|
||||
import Text from "@/components/Text";
|
||||
import { ConstEnv } from "@/constants/ConstEnv";
|
||||
import Styles from "@/constants/Styles";
|
||||
import { apiGetDivisionMember } from "@/lib/api";
|
||||
import { setMemberChoose } from "@/lib/memberChoose";
|
||||
@@ -93,7 +94,7 @@ export default function AddMemberCreateTask() {
|
||||
<ImageWithLabel
|
||||
key={index}
|
||||
label={item.name}
|
||||
src={`https://wibu-storage.wibudev.com/api/files/${item.img}`}
|
||||
src={`${ConstEnv.url_storage}/files/${item.img}`}
|
||||
onClick={() => onChoose(item.idUser, item.name, item.img)}
|
||||
/>
|
||||
))
|
||||
@@ -118,13 +119,13 @@ export default function AddMemberCreateTask() {
|
||||
}}
|
||||
>
|
||||
<View style={[Styles.rowItemsCenter]}>
|
||||
<ImageUser src={`https://wibu-storage.wibudev.com/api/files/${item.img}`} border />
|
||||
<ImageUser src={`${ConstEnv.url_storage}/files/${item.img}`} border />
|
||||
<View style={[Styles.ml10]}>
|
||||
<Text style={[Styles.textDefault]}>{item.name}</Text>
|
||||
</View>
|
||||
</View>
|
||||
{
|
||||
selectMember.some((i: any) => i.idUser == item.idUser) && <AntDesign name="check" size={20} color={'black'}/>
|
||||
selectMember.some((i: any) => i.idUser == item.idUser) && <AntDesign name="check" size={20} color={'black'} />
|
||||
}
|
||||
</Pressable>
|
||||
)
|
||||
|
||||
@@ -1,17 +1,22 @@
|
||||
import ButtonBackHeader from "@/components/buttonBackHeader";
|
||||
import ButtonSaveHeader from "@/components/buttonSaveHeader";
|
||||
import { InputForm } from "@/components/inputForm";
|
||||
import ModalAddDetailTugasTask from "@/components/task/modalAddDetailTugasTask";
|
||||
import Text from "@/components/Text";
|
||||
import Styles from "@/constants/Styles";
|
||||
import { formatDateOnly } from "@/lib/fun_formatDateOnly";
|
||||
import { getDatesInRange } from "@/lib/fun_getDatesInRange";
|
||||
import { setTaskCreate } from "@/lib/taskCreate";
|
||||
import dayjs from "dayjs";
|
||||
import { useHeaderHeight } from '@react-navigation/elements';
|
||||
import { router, Stack } from "expo-router";
|
||||
import 'intl';
|
||||
import 'intl/locale-data/jsonp/id';
|
||||
import moment from "moment";
|
||||
import { useEffect, useState } from "react";
|
||||
import {
|
||||
KeyboardAvoidingView,
|
||||
Platform,
|
||||
Pressable,
|
||||
SafeAreaView,
|
||||
ScrollView,
|
||||
View
|
||||
@@ -20,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 CreateTaskAddTugas() {
|
||||
const headerHeight = useHeaderHeight();
|
||||
@@ -37,11 +41,12 @@ export default function CreateTaskAddTugas() {
|
||||
})
|
||||
const [title, setTitle] = useState('');
|
||||
const taskCreate = useSelector((state: any) => state.taskCreate)
|
||||
const [dsbButton, setDsbButton] = useState(true)
|
||||
const [dataDetail, setDataDetail] = useState<any>([])
|
||||
const [modalDetail, setModalDetail] = useState(false)
|
||||
|
||||
const from = range.startDate
|
||||
? dayjs(range.startDate).format("DD-MM-YYYY")
|
||||
: "";
|
||||
const to = range.endDate ? dayjs(range.endDate).format("DD-MM-YYYY") : "";
|
||||
const from = formatDateOnly(range.startDate, "DD-MM-YYYY")
|
||||
const to = formatDateOnly(range.endDate, "DD-MM-YYYY")
|
||||
|
||||
function checkAll() {
|
||||
if (from == "" || to == "" || title == "" || title == "null" || error.startDate || error.endDate || error.title) {
|
||||
@@ -62,18 +67,45 @@ export default function CreateTaskAddTugas() {
|
||||
}
|
||||
}
|
||||
|
||||
function checkButton() {
|
||||
if (range.startDate == null || range.endDate == null || range.startDate == undefined || range.endDate == undefined) {
|
||||
setDsbButton(true)
|
||||
setDataDetail([])
|
||||
} else {
|
||||
setDsbButton(false)
|
||||
const awal = formatDateOnly(range.startDate, "YYYY-MM-DD")
|
||||
const akhir = formatDateOnly(range.endDate, "YYYY-MM-DD")
|
||||
const datanya = getDatesInRange(awal, akhir)
|
||||
setDataDetail(datanya.map((item: any) => ({
|
||||
date: item,
|
||||
timeStart: null,
|
||||
timeEnd: null,
|
||||
})))
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
checkAll()
|
||||
}, [from, to, title, error])
|
||||
|
||||
useEffect(() => {
|
||||
checkButton()
|
||||
}, [range])
|
||||
|
||||
async function handleCreate() {
|
||||
try {
|
||||
const dataDetailFix = dataDetail.map((item: any) => ({
|
||||
date: moment(item.date, "DD-MM-YYYY").format("YYYY-MM-DD"),
|
||||
timeStart: item.timeStart,
|
||||
timeEnd: item.timeEnd,
|
||||
}))
|
||||
dispatch(setTaskCreate([...taskCreate, {
|
||||
title: title,
|
||||
dateStart: from,
|
||||
dateEnd: to,
|
||||
dateStartFix: dayjs(range.startDate).format("YYYY-MM-DD"),
|
||||
dateEndFix: dayjs(range.endDate).format("YYYY-MM-DD"),
|
||||
dateStartFix: formatDateOnly(range.startDate, "YYYY-MM-DD"),
|
||||
dateEndFix: formatDateOnly(range.endDate, "YYYY-MM-DD"),
|
||||
dataDetail: dataDetailFix
|
||||
}]))
|
||||
router.back();
|
||||
} catch (error) {
|
||||
@@ -151,6 +183,13 @@ export default function CreateTaskAddTugas() {
|
||||
{
|
||||
(error.endDate || error.startDate) && <Text style={[Styles.textInformation, Styles.cError, Styles.mt05]}>Tanggal tidak boleh kosong</Text>
|
||||
}
|
||||
<Pressable
|
||||
style={[Styles.btnTab, Styles.btnLainnya, dsbButton && Styles.btnDisabled]}
|
||||
disabled={dsbButton}
|
||||
onPress={() => { setModalDetail(true) }}
|
||||
>
|
||||
<Text style={[dsbButton ? Styles.cGray : Styles.cWhite]}>Detail</Text>
|
||||
</Pressable>
|
||||
</View>
|
||||
<InputForm
|
||||
label="Judul Tugas"
|
||||
@@ -168,6 +207,14 @@ export default function CreateTaskAddTugas() {
|
||||
</View>
|
||||
</ScrollView>
|
||||
</KeyboardAvoidingView>
|
||||
<ModalAddDetailTugasTask
|
||||
isVisible={modalDetail}
|
||||
setVisible={setModalDetail}
|
||||
dataTanggal={dataDetail}
|
||||
onSubmit={(data) => {
|
||||
setDataDetail(data)
|
||||
}}
|
||||
/>
|
||||
</SafeAreaView>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,20 +1,24 @@
|
||||
import ButtonBackHeader from "@/components/buttonBackHeader";
|
||||
import ButtonSaveHeader from "@/components/buttonSaveHeader";
|
||||
import { InputForm } from "@/components/inputForm";
|
||||
import ModalAddDetailTugasTask from "@/components/task/modalAddDetailTugasTask";
|
||||
import Text from "@/components/Text";
|
||||
import Styles from "@/constants/Styles";
|
||||
import { apiEditTaskTugas, apiGetTaskTugas } from "@/lib/api";
|
||||
import { formatDateOnly } from "@/lib/fun_formatDateOnly";
|
||||
import { getDatesInRange } from "@/lib/fun_getDatesInRange";
|
||||
import { setUpdateTask } from "@/lib/taskUpdate";
|
||||
import { useAuthSession } from "@/providers/AuthProvider";
|
||||
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 moment from "moment";
|
||||
import { useEffect, useState } from "react";
|
||||
import {
|
||||
KeyboardAvoidingView,
|
||||
Platform,
|
||||
Pressable,
|
||||
SafeAreaView,
|
||||
ScrollView,
|
||||
View
|
||||
@@ -38,6 +42,9 @@ export default function UpdateProjectTaskDivision() {
|
||||
const [year, setYear] = useState<any>();
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [disableBtn, setDisableBtn] = useState(false);
|
||||
const [dataDetail, setDataDetail] = useState<any>([])
|
||||
const [modalDetail, setModalDetail] = useState(false)
|
||||
const [dsbButton, setDsbButton] = useState(true)
|
||||
const [title, setTitle] = useState("");
|
||||
const [error, setError] = useState({
|
||||
startDate: false,
|
||||
@@ -45,10 +52,8 @@ export default function UpdateProjectTaskDivision() {
|
||||
title: false,
|
||||
});
|
||||
|
||||
const from = range.startDate
|
||||
? dayjs(range.startDate).format("DD-MM-YYYY")
|
||||
: "";
|
||||
const to = range.endDate ? dayjs(range.endDate).format("DD-MM-YYYY") : "";
|
||||
const from = formatDateOnly(range.startDate);
|
||||
const to = formatDateOnly(range.endDate);
|
||||
|
||||
async function handleLoad() {
|
||||
try {
|
||||
@@ -65,6 +70,22 @@ export default function UpdateProjectTaskDivision() {
|
||||
});
|
||||
setMonth(new Date(response.data.dateStart).getMonth());
|
||||
setYear(new Date(response.data.dateStart).getFullYear());
|
||||
|
||||
const response2 = await apiGetTaskTugas({
|
||||
user: hasil,
|
||||
id: detail,
|
||||
cat: "detailTask"
|
||||
});
|
||||
if (response2.data.length == 0) {
|
||||
const datanya = getDatesInRange(response.data.dateStart, response.data.dateEnd)
|
||||
setDataDetail(datanya.map((item: any) => ({
|
||||
date: item,
|
||||
timeStart: null,
|
||||
timeEnd: null,
|
||||
})))
|
||||
} else {
|
||||
setDataDetail(response2.data)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
} finally {
|
||||
@@ -79,8 +100,22 @@ export default function UpdateProjectTaskDivision() {
|
||||
async function handleEdit() {
|
||||
try {
|
||||
setLoadingSubmit(true)
|
||||
const dataDetailFix = dataDetail.map((item: any) => ({
|
||||
date: moment(item.date, "DD-MM-YYYY").format("YYYY-MM-DD"),
|
||||
timeStart: item.timeStart,
|
||||
timeEnd: item.timeEnd,
|
||||
}))
|
||||
const hasil = await decryptToken(String(token?.current));
|
||||
const response = await apiEditTaskTugas({ data: { title, dateStart: dayjs(range.startDate).format("YYYY-MM-DD"), dateEnd: dayjs(range.endDate).format("YYYY-MM-DD"), user: hasil }, id: detail });
|
||||
const response = await apiEditTaskTugas({
|
||||
data: {
|
||||
title,
|
||||
dateStart: formatDateOnly(range.startDate, "YYYY-MM-DD"),
|
||||
dateEnd: formatDateOnly(range.endDate, "YYYY-MM-DD"),
|
||||
user: hasil,
|
||||
dataDetail: dataDetailFix
|
||||
},
|
||||
id: detail
|
||||
});
|
||||
if (response.success) {
|
||||
dispatch(setUpdateTask({ ...update, task: !update.task }))
|
||||
Toast.show({ type: 'small', text1: 'Berhasil mengubah data', })
|
||||
@@ -123,10 +158,33 @@ export default function UpdateProjectTaskDivision() {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function checkButton() {
|
||||
if (range.startDate == null || range.endDate == null || range.startDate == undefined || range.endDate == undefined) {
|
||||
setDsbButton(true)
|
||||
setDataDetail([])
|
||||
} else {
|
||||
setDsbButton(false)
|
||||
const awal = formatDateOnly(range.startDate, "YYYY-MM-DD")
|
||||
const akhir = formatDateOnly(range.endDate, "YYYY-MM-DD")
|
||||
const datanya = getDatesInRange(awal, akhir)
|
||||
setDataDetail(datanya.map((item: any) => ({
|
||||
date: item,
|
||||
timeStart: null,
|
||||
timeEnd: null,
|
||||
})))
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
checkAll();
|
||||
}, [from, to, title, error]);
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
checkButton()
|
||||
}, [range])
|
||||
|
||||
return (
|
||||
<SafeAreaView>
|
||||
<Stack.Screen
|
||||
@@ -205,6 +263,13 @@ export default function UpdateProjectTaskDivision() {
|
||||
Tanggal tidak boleh kosong
|
||||
</Text>
|
||||
)}
|
||||
<Pressable
|
||||
style={[Styles.btnTab, Styles.btnLainnya, dsbButton && Styles.btnDisabled]}
|
||||
disabled={dsbButton}
|
||||
onPress={() => { setModalDetail(true) }}
|
||||
>
|
||||
<Text style={[dsbButton ? Styles.cGray : Styles.cWhite]}>Detail</Text>
|
||||
</Pressable>
|
||||
</View>
|
||||
<InputForm
|
||||
label="Judul Tugas"
|
||||
@@ -222,7 +287,14 @@ export default function UpdateProjectTaskDivision() {
|
||||
</View>
|
||||
</ScrollView>
|
||||
</KeyboardAvoidingView>
|
||||
|
||||
<ModalAddDetailTugasTask
|
||||
isVisible={modalDetail}
|
||||
setVisible={setModalDetail}
|
||||
dataTanggal={dataDetail}
|
||||
onSubmit={(data) => {
|
||||
setDataDetail(data)
|
||||
}}
|
||||
/>
|
||||
</SafeAreaView>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import ImageUser from "@/components/imageNew";
|
||||
import ImageWithLabel from "@/components/imageWithLabel";
|
||||
import InputSearch from "@/components/inputSearch";
|
||||
import Text from "@/components/Text";
|
||||
import { ConstEnv } from "@/constants/ConstEnv";
|
||||
import Styles from "@/constants/Styles";
|
||||
import { apiAddMemberDivision, apiGetDivisionOneDetail, apiGetUser } from "@/lib/api";
|
||||
import { setUpdateDivision } from "@/lib/divisionUpdate";
|
||||
@@ -124,7 +125,7 @@ export default function AddMemberDivision() {
|
||||
<ImageWithLabel
|
||||
key={index}
|
||||
label={item.name}
|
||||
src={`https://wibu-storage.wibudev.com/api/files/${item.img}`}
|
||||
src={`${ConstEnv.url_storage}/files/${item.img}`}
|
||||
onClick={() => onChoose(item.idUser, item.name, item.img)}
|
||||
/>
|
||||
))
|
||||
@@ -150,7 +151,7 @@ export default function AddMemberDivision() {
|
||||
}}
|
||||
>
|
||||
<View style={[Styles.rowItemsCenter]}>
|
||||
<ImageUser src={`https://wibu-storage.wibudev.com/api/files/${item.img}`} border />
|
||||
<ImageUser src={`${ConstEnv.url_storage}/files/${item.img}`} border />
|
||||
<View style={[Styles.ml10]}>
|
||||
<Text style={[Styles.textDefault]} numberOfLines={1} ellipsizeMode="tail">{item.name}</Text>
|
||||
{
|
||||
@@ -159,7 +160,7 @@ export default function AddMemberDivision() {
|
||||
</View>
|
||||
</View>
|
||||
{
|
||||
selectMember.some((i: any) => i.idUser == item.id) && <AntDesign name="check" size={20} color={'black'}/>
|
||||
selectMember.some((i: any) => i.idUser == item.id) && <AntDesign name="check" size={20} color={'black'} />
|
||||
}
|
||||
</Pressable>
|
||||
)
|
||||
|
||||
@@ -8,6 +8,7 @@ import SectionCancel from "@/components/sectionCancel"
|
||||
import Skeleton from "@/components/skeleton"
|
||||
import SkeletonTwoItem from "@/components/skeletonTwoItem"
|
||||
import { ColorsStatus } from "@/constants/ColorsStatus"
|
||||
import { ConstEnv } from "@/constants/ConstEnv"
|
||||
import Styles from "@/constants/Styles"
|
||||
import { apiDeleteMemberDivision, apiGetDivisionOneDetail, apiUpdateStatusAdminDivision } from "@/lib/api"
|
||||
import { useAuthSession } from "@/providers/AuthProvider"
|
||||
@@ -184,12 +185,11 @@ export default function InformationDivision() {
|
||||
dataMember.map((item, index) => {
|
||||
return (
|
||||
<BorderBottomItem
|
||||
width={55}
|
||||
key={index}
|
||||
borderType="bottom"
|
||||
onPress={() => { dataDetail?.isActive && handleChooseMember(item) }}
|
||||
icon={
|
||||
<ImageUser src={`https://wibu-storage.wibudev.com/api/files/${item.img}`} size="sm" />
|
||||
<ImageUser src={`${ConstEnv.url_storage}/files/${item.img}`} size="sm" />
|
||||
}
|
||||
title={item.name}
|
||||
rightTopInfo={item.isAdmin ? "Admin" : "Anggota"}
|
||||
|
||||
@@ -2,6 +2,7 @@ import ButtonBackHeader from "@/components/buttonBackHeader";
|
||||
import ButtonSaveHeader from "@/components/buttonSaveHeader";
|
||||
import ImageUser from "@/components/imageNew";
|
||||
import Text from "@/components/Text";
|
||||
import { ConstEnv } from "@/constants/ConstEnv";
|
||||
import Styles from "@/constants/Styles";
|
||||
import { apiCreateDivision } from "@/lib/api";
|
||||
import { setFormCreateDivision } from "@/lib/divisionCreate";
|
||||
@@ -103,7 +104,7 @@ export default function CreateDivisionAddAdmin() {
|
||||
}}
|
||||
>
|
||||
<View style={[Styles.rowItemsCenter, Styles.w70]}>
|
||||
<ImageUser src={`https://wibu-storage.wibudev.com/api/files/${item.img}`} border />
|
||||
<ImageUser src={`${ConstEnv.url_storage}/files/${item.img}`} border />
|
||||
<View style={[Styles.ml10]}>
|
||||
<Text style={[Styles.textDefault]} numberOfLines={1} ellipsizeMode="tail">{item.name}</Text>
|
||||
{
|
||||
@@ -112,7 +113,7 @@ export default function CreateDivisionAddAdmin() {
|
||||
</View>
|
||||
</View>
|
||||
{
|
||||
selectMember.some((i: any) => i == item.idUser) && <AntDesign name="check" size={20} color={'black'}/>
|
||||
selectMember.some((i: any) => i == item.idUser) && <AntDesign name="check" size={20} color={'black'} />
|
||||
}
|
||||
</Pressable>
|
||||
)
|
||||
|
||||
@@ -4,6 +4,7 @@ import ImageUser from "@/components/imageNew";
|
||||
import ImageWithLabel from "@/components/imageWithLabel";
|
||||
import InputSearch from "@/components/inputSearch";
|
||||
import Text from "@/components/Text";
|
||||
import { ConstEnv } from "@/constants/ConstEnv";
|
||||
import Styles from "@/constants/Styles";
|
||||
import { apiGetUser } from "@/lib/api";
|
||||
import { setFormCreateDivision } from "@/lib/divisionCreate";
|
||||
@@ -86,7 +87,7 @@ export default function CreateDivisionAddMember() {
|
||||
<ImageWithLabel
|
||||
key={index}
|
||||
label={item.name}
|
||||
src={`https://wibu-storage.wibudev.com/api/files/${item.img}`}
|
||||
src={`${ConstEnv.url_storage}/files/${item.img}`}
|
||||
onClick={() => onChoose(item.idUser, item.name, item.img)}
|
||||
/>
|
||||
))
|
||||
@@ -112,7 +113,7 @@ export default function CreateDivisionAddMember() {
|
||||
}}
|
||||
>
|
||||
<View style={[Styles.rowItemsCenter, Styles.w70]}>
|
||||
<ImageUser src={`https://wibu-storage.wibudev.com/api/files/${item.img}`} border />
|
||||
<ImageUser src={`${ConstEnv.url_storage}/files/${item.img}`} border />
|
||||
<View style={[Styles.ml10]}>
|
||||
<Text style={[Styles.textDefault]} numberOfLines={1} ellipsizeMode="tail">{item.name}</Text>
|
||||
{
|
||||
@@ -121,7 +122,7 @@ export default function CreateDivisionAddMember() {
|
||||
</View>
|
||||
</View>
|
||||
{
|
||||
selectMember.some((i: any) => i.idUser == item.id) && <AntDesign name="check" size={20} color={'black'}/>
|
||||
selectMember.some((i: any) => i.idUser == item.id) && <AntDesign name="check" size={20} color={'black'} />
|
||||
}
|
||||
</Pressable>
|
||||
)
|
||||
|
||||
@@ -4,10 +4,12 @@ import { InputForm } from "@/components/inputForm";
|
||||
import ModalSelect from "@/components/modalSelect";
|
||||
import SelectForm from "@/components/selectForm";
|
||||
import Text from "@/components/Text";
|
||||
import { ConstEnv } from "@/constants/ConstEnv";
|
||||
import Styles from "@/constants/Styles";
|
||||
import { apiEditProfile, apiGetProfile } from "@/lib/api";
|
||||
import { setEntities } from "@/lib/entitiesSlice";
|
||||
import { useAuthSession } from "@/providers/AuthProvider";
|
||||
import { MaterialCommunityIcons } from "@expo/vector-icons";
|
||||
import * as ImagePicker from "expo-image-picker";
|
||||
import { router, Stack } from "expo-router";
|
||||
import { useEffect, useState } from "react";
|
||||
@@ -229,8 +231,8 @@ export default function EditProfile() {
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<ScrollView>
|
||||
<View style={[Styles.p15, Styles.mb100]}>
|
||||
<ScrollView style={[Styles.h100]}>
|
||||
<View style={[Styles.p15]}>
|
||||
<View style={{ justifyContent: "center", alignItems: "center" }}>
|
||||
{
|
||||
selectedImage != undefined ? (
|
||||
@@ -244,16 +246,20 @@ export default function EditProfile() {
|
||||
style={[Styles.userProfileBig]}
|
||||
onError={() => { setErrorImg(true) }}
|
||||
/>
|
||||
<View style={[Styles.absoluteIconPicker]}>
|
||||
<MaterialCommunityIcons name="image" color={'white'} size={15} />
|
||||
</View>
|
||||
</Pressable>
|
||||
) : (
|
||||
<Pressable onPress={pickImageAsync}>
|
||||
{
|
||||
<Image
|
||||
source={errorImg ? require("../../assets/images/user.jpg") : { uri: `https://wibu-storage.wibudev.com/api/files/${data?.img}` }}
|
||||
style={[Styles.userProfileBig]}
|
||||
onError={() => { setErrorImg(true) }}
|
||||
/>
|
||||
}
|
||||
<Image
|
||||
source={errorImg ? require("../../assets/images/user.jpg") : { uri: `${ConstEnv.url_storage}/files/${data?.img}` }}
|
||||
style={[Styles.userProfileBig]}
|
||||
onError={() => { setErrorImg(true) }}
|
||||
/>
|
||||
<View style={[Styles.absoluteIconPicker]}>
|
||||
<MaterialCommunityIcons name="image" color={'white'} size={15} />
|
||||
</View>
|
||||
</Pressable>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ import { setUpdateGroup } from "@/lib/groupSlice";
|
||||
import { useAuthSession } from "@/providers/AuthProvider";
|
||||
import { AntDesign, Feather, MaterialCommunityIcons } from "@expo/vector-icons";
|
||||
import { useEffect, useState } from "react";
|
||||
import { RefreshControl, SafeAreaView, ScrollView, View } from "react-native";
|
||||
import { RefreshControl, View, VirtualizedList } from "react-native";
|
||||
import Toast from "react-native-toast-message";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
|
||||
@@ -116,9 +116,17 @@ export default function Index() {
|
||||
}
|
||||
|
||||
|
||||
const getItem = (_data: unknown, index: number): Props => ({
|
||||
id: data[index].id,
|
||||
name: data[index].name,
|
||||
isActive: data[index].isActive,
|
||||
});
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<SafeAreaView>
|
||||
<View style={[Styles.p15]}>
|
||||
<View style={[Styles.p15, { flex: 1 }]}>
|
||||
<View>
|
||||
<View style={[Styles.wrapBtnTab]}>
|
||||
<ButtonTab
|
||||
active={status == "false" ? "false" : "true"}
|
||||
@@ -136,51 +144,53 @@ export default function Index() {
|
||||
n={2} />
|
||||
</View>
|
||||
<InputSearch onChange={setSearch} />
|
||||
<ScrollView
|
||||
refreshControl={
|
||||
<RefreshControl
|
||||
refreshing={refreshing}
|
||||
onRefresh={handleRefresh}
|
||||
/>
|
||||
}
|
||||
style={[Styles.h100]}
|
||||
>
|
||||
<View>
|
||||
{
|
||||
|
||||
loading ?
|
||||
arrSkeleton.map((item, index) => {
|
||||
</View>
|
||||
<View style={{ flex: 2 }}>
|
||||
{
|
||||
loading ?
|
||||
arrSkeleton.map((item, index) => {
|
||||
return (
|
||||
<SkeletonTwoItem key={index} />
|
||||
)
|
||||
})
|
||||
:
|
||||
data.length > 0 ?
|
||||
<VirtualizedList
|
||||
data={data}
|
||||
getItemCount={() => data.length}
|
||||
getItem={getItem}
|
||||
renderItem={({ item, index }: { item: Props, index: number }) => {
|
||||
return (
|
||||
<SkeletonTwoItem key={index} />
|
||||
<BorderBottomItem
|
||||
key={index}
|
||||
onPress={() => {
|
||||
setIdChoose(item.id)
|
||||
setActiveChoose(item.isActive)
|
||||
setTitleChoose(item.name)
|
||||
setModal(true)
|
||||
}}
|
||||
borderType="all"
|
||||
icon={
|
||||
<View style={[Styles.iconContent, ColorsStatus.lightGreen]}>
|
||||
<MaterialCommunityIcons name="office-building-outline" size={25} color={'#384288'} />
|
||||
</View>
|
||||
}
|
||||
title={item.name}
|
||||
/>
|
||||
)
|
||||
})
|
||||
:
|
||||
data.length > 0 ?
|
||||
data.map((item, index) => {
|
||||
return (
|
||||
<BorderBottomItem
|
||||
key={index}
|
||||
onPress={() => {
|
||||
setIdChoose(item.id)
|
||||
setActiveChoose(item.isActive)
|
||||
setTitleChoose(item.name)
|
||||
setModal(true)
|
||||
}}
|
||||
borderType="all"
|
||||
icon={
|
||||
<View style={[Styles.iconContent, ColorsStatus.lightGreen]}>
|
||||
<MaterialCommunityIcons name="office-building-outline" size={25} color={'#384288'} />
|
||||
</View>
|
||||
}
|
||||
title={item.name}
|
||||
/>
|
||||
)
|
||||
})
|
||||
:
|
||||
<Text style={[Styles.textDefault, Styles.cGray, { textAlign: 'center' }]}>Tidak ada data</Text>
|
||||
}
|
||||
</View>
|
||||
</ScrollView>
|
||||
}}
|
||||
keyExtractor={(item, index) => String(index)}
|
||||
showsVerticalScrollIndicator={false}
|
||||
refreshControl={
|
||||
<RefreshControl
|
||||
refreshing={refreshing}
|
||||
onRefresh={handleRefresh}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
:
|
||||
<Text style={[Styles.textDefault, Styles.cGray, { textAlign: 'center' }]}>Tidak ada data</Text>
|
||||
}
|
||||
</View>
|
||||
|
||||
<DrawerBottom animation="slide" isVisible={isModal} setVisible={() => setModal(false)} title={titleChoose}>
|
||||
@@ -228,8 +238,7 @@ export default function Index() {
|
||||
</View>
|
||||
</View>
|
||||
</DrawerBottom>
|
||||
</View >
|
||||
|
||||
|
||||
</SafeAreaView>
|
||||
)
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import LabelStatus from "@/components/labelStatus";
|
||||
import HeaderRightMemberDetail from "@/components/member/headerMemberDetail";
|
||||
import Skeleton from "@/components/skeleton";
|
||||
import Text from "@/components/Text";
|
||||
import { ConstEnv } from "@/constants/ConstEnv";
|
||||
import { valueRoleUser } from "@/constants/RoleUser";
|
||||
import Styles from "@/constants/Styles";
|
||||
import { apiGetProfile } from "@/lib/api";
|
||||
@@ -93,7 +94,7 @@ export default function MemberDetail() {
|
||||
</>
|
||||
:
|
||||
<>
|
||||
<ImageUser src={`https://wibu-storage.wibudev.com/api/files/${data?.img}`} size="lg" />
|
||||
<ImageUser src={`${ConstEnv.url_storage}/files/${data?.img}`} size="lg" />
|
||||
<Text style={[Styles.textSubtitle, Styles.cWhite, Styles.mt10]}>{data?.name}</Text>
|
||||
<Text style={[Styles.textMediumNormal, Styles.cWhite]}>{data?.role}</Text>
|
||||
</>
|
||||
|
||||
@@ -10,6 +10,7 @@ import { apiCreateUser } from "@/lib/api";
|
||||
import { setUpdateMember } from "@/lib/memberSlice";
|
||||
import { useAuthSession } from "@/providers/AuthProvider";
|
||||
import { MaterialCommunityIcons } from "@expo/vector-icons";
|
||||
import { useHeaderHeight } from '@react-navigation/elements';
|
||||
import * as ImagePicker from "expo-image-picker";
|
||||
import { router, Stack } from "expo-router";
|
||||
import { useEffect, useState } from "react";
|
||||
@@ -24,7 +25,6 @@ import {
|
||||
} from "react-native";
|
||||
import Toast from "react-native-toast-message";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { useHeaderHeight } from '@react-navigation/elements';
|
||||
|
||||
export default function CreateMember() {
|
||||
const headerHeight = useHeaderHeight();
|
||||
@@ -147,7 +147,7 @@ export default function CreateMember() {
|
||||
}, [error, dataForm])
|
||||
|
||||
useEffect(() => {
|
||||
if(entityUser.role !="supadmin" && entityUser.role != "developer"){
|
||||
if (entityUser.role != "supadmin" && entityUser.role != "developer") {
|
||||
validationForm("group", entities.idGroup, entities.group)
|
||||
}
|
||||
}, [])
|
||||
@@ -173,7 +173,7 @@ export default function CreateMember() {
|
||||
fd.append("file", "undefined")
|
||||
}
|
||||
|
||||
const response = await apiCreateUser({data: fd})
|
||||
const response = await apiCreateUser({ data: fd })
|
||||
if (response.success) {
|
||||
Toast.show({ type: 'small', text1: 'Berhasil menambahkan data', })
|
||||
dispatch(setUpdateMember(!update))
|
||||
@@ -184,7 +184,7 @@ export default function CreateMember() {
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
Toast.show({ type: 'small', text1: 'Terjadi kesalahan', })
|
||||
}finally{
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
@@ -227,9 +227,9 @@ export default function CreateMember() {
|
||||
}}
|
||||
/>
|
||||
<KeyboardAvoidingView
|
||||
style={[Styles.h100]}
|
||||
behavior={Platform.OS === 'ios' ? 'padding' : undefined}
|
||||
keyboardVerticalOffset={headerHeight}
|
||||
style={[Styles.h100]}
|
||||
behavior={Platform.OS === 'ios' ? 'padding' : undefined}
|
||||
keyboardVerticalOffset={headerHeight}
|
||||
>
|
||||
<ScrollView>
|
||||
<View style={[Styles.p15]}>
|
||||
@@ -237,17 +237,16 @@ export default function CreateMember() {
|
||||
{selectedImage != undefined ? (
|
||||
<Pressable onPress={pickImageAsync}>
|
||||
<Image src={selectedImage} style={[Styles.userProfileBig]} />
|
||||
<View style={[Styles.absoluteIconPicker]}>
|
||||
<MaterialCommunityIcons name="image" color={'white'} size={15} />
|
||||
</View>
|
||||
</Pressable>
|
||||
) : (
|
||||
<Pressable
|
||||
onPress={pickImageAsync}
|
||||
style={[Styles.iconContent, ColorsStatus.gray]}
|
||||
>
|
||||
<MaterialCommunityIcons
|
||||
name="account-tie"
|
||||
size={100}
|
||||
color={"gray"}
|
||||
/>
|
||||
<Pressable onPress={pickImageAsync} style={[Styles.iconContent, ColorsStatus.gray]} >
|
||||
<MaterialCommunityIcons name="account-tie" size={85} color={"gray"} />
|
||||
<View style={[Styles.absoluteIconPicker]}>
|
||||
<MaterialCommunityIcons name="image" color={'white'} size={15} />
|
||||
</View>
|
||||
</Pressable>
|
||||
)}
|
||||
</View>
|
||||
|
||||
@@ -4,10 +4,13 @@ import { InputForm } from "@/components/inputForm";
|
||||
import ModalSelect from "@/components/modalSelect";
|
||||
import SelectForm from "@/components/selectForm";
|
||||
import Text from "@/components/Text";
|
||||
import { ConstEnv } from "@/constants/ConstEnv";
|
||||
import Styles from "@/constants/Styles";
|
||||
import { apiEditUser, apiGetProfile } from "@/lib/api";
|
||||
import { setUpdateMember } from "@/lib/memberSlice";
|
||||
import { useAuthSession } from "@/providers/AuthProvider";
|
||||
import { MaterialCommunityIcons } from "@expo/vector-icons";
|
||||
import { useHeaderHeight } from '@react-navigation/elements';
|
||||
import * as ImagePicker from "expo-image-picker";
|
||||
import { router, Stack, useLocalSearchParams } from "expo-router";
|
||||
import { useEffect, useState } from "react";
|
||||
@@ -22,7 +25,6 @@ import {
|
||||
} from "react-native";
|
||||
import Toast from "react-native-toast-message";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { useHeaderHeight } from '@react-navigation/elements';
|
||||
|
||||
type Props = {
|
||||
id: string;
|
||||
@@ -82,7 +84,7 @@ export default function EditMember() {
|
||||
try {
|
||||
const response = await apiGetProfile({ id: id });
|
||||
setData(response.data);
|
||||
setSelectedImage({ uri: `https://wibu-storage.wibudev.com/api/files/${response.data.img}`, });
|
||||
setSelectedImage({ uri: `${ConstEnv.url_storage}/files/${response.data.img}`, });
|
||||
setChoosePosition({
|
||||
val: response.data.idPosition,
|
||||
label: response.data.position,
|
||||
@@ -208,7 +210,7 @@ export default function EditMember() {
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
Toast.show({ type: 'small', text1: 'Terjadi kesalahan', })
|
||||
}finally{
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
@@ -268,13 +270,14 @@ export default function EditMember() {
|
||||
{
|
||||
errorImg ?
|
||||
<Pressable onPress={pickImageAsync}>
|
||||
{
|
||||
<Image
|
||||
source={errorImg ? require("../../../../assets/images/user.jpg") : { uri: `https://wibu-storage.wibudev.com/api/files/${data?.img}` }}
|
||||
style={[Styles.userProfileBig]}
|
||||
onError={() => { setErrorImg(true) }}
|
||||
/>
|
||||
}
|
||||
<Image
|
||||
source={errorImg ? require("../../../../assets/images/user.jpg") : { uri: `${ConstEnv.url_storage}/files/${data?.img}` }}
|
||||
style={[Styles.userProfileBig]}
|
||||
onError={() => { setErrorImg(true) }}
|
||||
/>
|
||||
<View style={[Styles.absoluteIconPicker]}>
|
||||
<MaterialCommunityIcons name="image" color={'white'} size={15} />
|
||||
</View>
|
||||
</Pressable>
|
||||
:
|
||||
selectedImage != undefined ? (
|
||||
@@ -288,6 +291,9 @@ export default function EditMember() {
|
||||
style={[Styles.userProfileBig]}
|
||||
onError={() => { setErrorImg(true) }}
|
||||
/>
|
||||
<View style={[Styles.absoluteIconPicker]}>
|
||||
<MaterialCommunityIcons name="image" color={'white'} size={15} />
|
||||
</View>
|
||||
</Pressable>
|
||||
) : (
|
||||
<Image
|
||||
|
||||
@@ -4,6 +4,7 @@ import ImageUser from "@/components/imageNew";
|
||||
import InputSearch from "@/components/inputSearch";
|
||||
import SkeletonTwoItem from "@/components/skeletonTwoItem";
|
||||
import Text from "@/components/Text";
|
||||
import { ConstEnv } from "@/constants/ConstEnv";
|
||||
import Styles from "@/constants/Styles";
|
||||
import { apiGetUser } from "@/lib/api";
|
||||
import { useAuthSession } from "@/providers/AuthProvider";
|
||||
@@ -150,7 +151,7 @@ export default function Index() {
|
||||
onPress={() => { router.push(`/member/${item.id}`) }}
|
||||
borderType="all"
|
||||
icon={
|
||||
<ImageUser src={`https://wibu-storage.wibudev.com/api/files/${item.img}`} />
|
||||
<ImageUser src={`${ConstEnv.url_storage}/files/${item.img}`} />
|
||||
}
|
||||
title={item.name}
|
||||
subtitle={`${item.group} - ${item.position}`}
|
||||
|
||||
@@ -136,7 +136,6 @@ export default function Notification() {
|
||||
return (
|
||||
<BorderBottomItem
|
||||
borderType="bottom"
|
||||
width={55}
|
||||
icon={
|
||||
<View style={[Styles.iconContent, item.isRead ? ColorsStatus.secondary : ColorsStatus.primary]}>
|
||||
<Feather name="bell" size={25} color="white" />
|
||||
|
||||
@@ -16,7 +16,7 @@ import { useAuthSession } from "@/providers/AuthProvider";
|
||||
import { AntDesign, Feather, MaterialCommunityIcons } from "@expo/vector-icons";
|
||||
import { useLocalSearchParams } from "expo-router";
|
||||
import { useEffect, useState } from "react";
|
||||
import { RefreshControl, SafeAreaView, ScrollView, View } from "react-native";
|
||||
import { RefreshControl, View, VirtualizedList } from "react-native";
|
||||
import Toast from "react-native-toast-message";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
|
||||
@@ -129,9 +129,18 @@ export default function Index() {
|
||||
setRefreshing(false)
|
||||
};
|
||||
|
||||
|
||||
const getItem = (_data: unknown, index: number): Props => ({
|
||||
id: data[index].id,
|
||||
name: data[index].name,
|
||||
idGroup: data[index].idGroup,
|
||||
group: data[index].group,
|
||||
isActive: data[index].isActive,
|
||||
});
|
||||
|
||||
return (
|
||||
<SafeAreaView>
|
||||
<View style={[Styles.p15]}>
|
||||
<View style={[Styles.p15, { flex: 1 }]}>
|
||||
<View>
|
||||
<View style={[Styles.wrapBtnTab]}>
|
||||
<ButtonTab
|
||||
active={status == "false" ? "false" : "true"}
|
||||
@@ -155,47 +164,50 @@ export default function Index() {
|
||||
<Text>Filter : {nameGroup}</Text>
|
||||
</View>
|
||||
}
|
||||
<ScrollView
|
||||
style={[Styles.h100]}
|
||||
refreshControl={
|
||||
<RefreshControl
|
||||
refreshing={refreshing}
|
||||
onRefresh={handleRefresh}
|
||||
/>
|
||||
}>
|
||||
<View>
|
||||
{
|
||||
loading ?
|
||||
arrSkeleton.map((item, index) => {
|
||||
return (
|
||||
<SkeletonTwoItem key={index} />
|
||||
)
|
||||
})
|
||||
:
|
||||
data.length > 0 ?
|
||||
data.map((item, index) => {
|
||||
return (
|
||||
<BorderBottomItem
|
||||
key={index}
|
||||
onPress={() => { handleChooseData(item.id, item.name, item.isActive, item.idGroup) }}
|
||||
borderType="all"
|
||||
icon={
|
||||
<View style={[Styles.iconContent, ColorsStatus.lightGreen]}>
|
||||
<MaterialCommunityIcons name="account-tie" size={25} color={'#384288'} />
|
||||
</View>
|
||||
}
|
||||
title={item.name}
|
||||
subtitle={item.group}
|
||||
/>
|
||||
)
|
||||
})
|
||||
:
|
||||
<Text style={[Styles.textDefault, Styles.cGray, { textAlign: 'center' }]}>Tidak ada data</Text>
|
||||
}
|
||||
</View>
|
||||
</ScrollView>
|
||||
</View>
|
||||
|
||||
<View style={[{ flex: 2 }]}>
|
||||
{
|
||||
loading ?
|
||||
arrSkeleton.map((item, index) => {
|
||||
return (
|
||||
<SkeletonTwoItem key={index} />
|
||||
)
|
||||
})
|
||||
:
|
||||
data.length > 0 ?
|
||||
<VirtualizedList
|
||||
data={data}
|
||||
getItemCount={() => data.length}
|
||||
getItem={getItem}
|
||||
renderItem={({ item, index }: { item: Props, index: number }) => {
|
||||
return (
|
||||
<BorderBottomItem
|
||||
key={index}
|
||||
onPress={() => { handleChooseData(item.id, item.name, item.isActive, item.idGroup) }}
|
||||
borderType="all"
|
||||
icon={
|
||||
<View style={[Styles.iconContent, ColorsStatus.lightGreen]}>
|
||||
<MaterialCommunityIcons name="account-tie" size={25} color={'#384288'} />
|
||||
</View>
|
||||
}
|
||||
title={item.name}
|
||||
subtitle={item.group}
|
||||
/>
|
||||
)
|
||||
}}
|
||||
keyExtractor={(item, index) => String(index)}
|
||||
showsVerticalScrollIndicator={false}
|
||||
refreshControl={
|
||||
<RefreshControl
|
||||
refreshing={refreshing}
|
||||
onRefresh={handleRefresh}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
:
|
||||
<Text style={[Styles.textDefault, Styles.cGray, { textAlign: 'center' }]}>Tidak ada data</Text>
|
||||
}
|
||||
</View>
|
||||
<DrawerBottom animation="slide" isVisible={isModal} setVisible={() => setModal(false)} title={chooseData.name}>
|
||||
<View style={Styles.rowItemsCenter}>
|
||||
<MenuItemRow
|
||||
@@ -243,7 +255,6 @@ export default function Index() {
|
||||
</View>
|
||||
</View>
|
||||
</DrawerBottom>
|
||||
|
||||
</SafeAreaView>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
@@ -3,6 +3,7 @@ import ButtonBackHeader from "@/components/buttonBackHeader";
|
||||
import { ButtonHeader } from "@/components/buttonHeader";
|
||||
import ItemDetailMember from "@/components/itemDetailMember";
|
||||
import Text from "@/components/Text";
|
||||
import { ConstEnv } from "@/constants/ConstEnv";
|
||||
import Styles from "@/constants/Styles";
|
||||
import { useAuthSession } from "@/providers/AuthProvider";
|
||||
import { AntDesign } from "@expo/vector-icons";
|
||||
@@ -41,7 +42,7 @@ export default function Profile() {
|
||||
<View style={{ flexDirection: 'column' }}>
|
||||
<View style={[Styles.wrapHeadViewMember]}>
|
||||
<Image
|
||||
source={error ? require("../../assets/images/user.jpg") : { uri: `https://wibu-storage.wibudev.com/api/files/${entities.img}` }}
|
||||
source={error ? require("../../assets/images/user.jpg") : { uri: `${ConstEnv.url_storage}/files/${entities.img}` }}
|
||||
onError={() => { setError(true) }}
|
||||
style={[Styles.userProfileBig]}
|
||||
/>
|
||||
|
||||
@@ -4,6 +4,7 @@ import ImageUser from "@/components/imageNew";
|
||||
import ImageWithLabel from "@/components/imageWithLabel";
|
||||
import InputSearch from "@/components/inputSearch";
|
||||
import Text from "@/components/Text";
|
||||
import { ConstEnv } from "@/constants/ConstEnv";
|
||||
import Styles from "@/constants/Styles";
|
||||
import { apiAddMemberProject, apiGetProjectOne, apiGetUser } from "@/lib/api";
|
||||
import { setUpdateProject } from "@/lib/projectUpdate";
|
||||
@@ -122,7 +123,7 @@ export default function AddMemberProject() {
|
||||
<ImageWithLabel
|
||||
key={index}
|
||||
label={item.name}
|
||||
src={`https://wibu-storage.wibudev.com/api/files/${item.img}`}
|
||||
src={`${ConstEnv.url_storage}/files/${item.img}`}
|
||||
onClick={() => onChoose(item.idUser, item.name, item.img)}
|
||||
/>
|
||||
))
|
||||
@@ -147,7 +148,7 @@ export default function AddMemberProject() {
|
||||
}}
|
||||
>
|
||||
<View style={[Styles.rowItemsCenter, Styles.w80]}>
|
||||
<ImageUser src={`https://wibu-storage.wibudev.com/api/files/${item.img}`} border />
|
||||
<ImageUser src={`${ConstEnv.url_storage}/files/${item.img}`} border />
|
||||
<View style={[Styles.ml10]}>
|
||||
<Text style={[Styles.textDefault]} numberOfLines={1}>{item.name}</Text>
|
||||
{
|
||||
@@ -156,7 +157,7 @@ export default function AddMemberProject() {
|
||||
</View>
|
||||
</View>
|
||||
{
|
||||
selectMember.some((i: any) => i.idUser == item.id) && <AntDesign name="check" size={20} color={'black'}/>
|
||||
selectMember.some((i: any) => i.idUser == item.id) && <AntDesign name="check" size={20} color={'black'} />
|
||||
}
|
||||
</Pressable>
|
||||
)
|
||||
|
||||
@@ -1,19 +1,24 @@
|
||||
import ButtonBackHeader from "@/components/buttonBackHeader";
|
||||
import ButtonSaveHeader from "@/components/buttonSaveHeader";
|
||||
import { InputForm } from "@/components/inputForm";
|
||||
import ModalAddDetailTugasProject from "@/components/project/modalAddDetailTugasProject";
|
||||
import Text from "@/components/Text";
|
||||
import Styles from "@/constants/Styles";
|
||||
import { apiCreateProjectTask } from "@/lib/api";
|
||||
import { formatDateOnly } from "@/lib/fun_formatDateOnly";
|
||||
import { getDatesInRange } from "@/lib/fun_getDatesInRange";
|
||||
import { setUpdateProject } from "@/lib/projectUpdate";
|
||||
import { useAuthSession } from "@/providers/AuthProvider";
|
||||
import { useHeaderHeight } from '@react-navigation/elements';
|
||||
import { router, Stack, useLocalSearchParams } from "expo-router";
|
||||
import 'intl';
|
||||
import 'intl/locale-data/jsonp/id';
|
||||
import dayjs from "dayjs";
|
||||
import { router, Stack, useLocalSearchParams } from "expo-router";
|
||||
import moment from "moment";
|
||||
import { useEffect, useState } from "react";
|
||||
import {
|
||||
KeyboardAvoidingView,
|
||||
Platform,
|
||||
Pressable,
|
||||
SafeAreaView,
|
||||
ScrollView,
|
||||
View
|
||||
@@ -23,13 +28,14 @@ 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();
|
||||
const { token, decryptToken } = useAuthSession()
|
||||
const dispatch = useDispatch()
|
||||
const update = useSelector((state: any) => state.projectUpdate)
|
||||
const [dataDetail, setDataDetail] = useState<any>([])
|
||||
const [modalDetail, setModalDetail] = useState(false)
|
||||
const { id } = useLocalSearchParams<{ id: string }>();
|
||||
const [disable, setDisable] = useState(true);
|
||||
const [range, setRange] = useState<{
|
||||
@@ -43,11 +49,10 @@ export default function ProjectAddTask() {
|
||||
})
|
||||
const [title, setTitle] = useState('');
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [dsbButton, setDsbButton] = useState(true)
|
||||
|
||||
const from = range.startDate
|
||||
? dayjs(range.startDate).format("DD-MM-YYYY")
|
||||
: "";
|
||||
const to = range.endDate ? dayjs(range.endDate).format("DD-MM-YYYY") : "";
|
||||
const from = formatDateOnly(range.startDate);
|
||||
const to = formatDateOnly(range.endDate);
|
||||
|
||||
function checkAll() {
|
||||
if (from == "" || to == "" || title == "" || title == "null" || error.startDate || error.endDate || error.title) {
|
||||
@@ -68,15 +73,50 @@ export default function ProjectAddTask() {
|
||||
}
|
||||
}
|
||||
|
||||
function checkButton() {
|
||||
if (range.startDate == null || range.endDate == null || range.startDate == undefined || range.endDate == undefined) {
|
||||
setDsbButton(true)
|
||||
setDataDetail([])
|
||||
} else {
|
||||
setDsbButton(false)
|
||||
const awal = formatDateOnly(range.startDate, "YYYY-MM-DD")
|
||||
const akhir = formatDateOnly(range.endDate, "YYYY-MM-DD")
|
||||
const datanya = getDatesInRange(awal, akhir)
|
||||
setDataDetail(datanya.map((item: any) => ({
|
||||
date: item,
|
||||
timeStart: null,
|
||||
timeEnd: null,
|
||||
})))
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
checkAll()
|
||||
}, [from, to, title, error])
|
||||
|
||||
useEffect(() => {
|
||||
checkButton()
|
||||
}, [range])
|
||||
|
||||
|
||||
async function handleCreate() {
|
||||
try {
|
||||
setLoading(true)
|
||||
const dataDetailFix = dataDetail.map((item: any) => ({
|
||||
date: moment(item.date, "DD-MM-YYYY").format("YYYY-MM-DD"),
|
||||
timeStart: item.timeStart,
|
||||
timeEnd: item.timeEnd,
|
||||
}))
|
||||
const hasil = await decryptToken(String(token?.current));
|
||||
const response = await apiCreateProjectTask({ data: { name: title, dateStart: dayjs(range.startDate).format("YYYY-MM-DD"), dateEnd: dayjs(range.endDate).format("YYYY-MM-DD"), user: hasil }, id });
|
||||
const response = await apiCreateProjectTask({
|
||||
data: {
|
||||
name: title,
|
||||
dateStart: formatDateOnly(range.startDate, "YYYY-MM-DD"),
|
||||
dateEnd: formatDateOnly(range.endDate, "YYYY-MM-DD"),
|
||||
user: hasil,
|
||||
dataDetail: dataDetailFix
|
||||
}, id
|
||||
});
|
||||
if (response.success) {
|
||||
dispatch(setUpdateProject({ ...update, task: !update.task, progress: !update.progress }))
|
||||
Toast.show({ type: 'small', text1: 'Berhasil menambah data', })
|
||||
@@ -125,7 +165,7 @@ export default function ProjectAddTask() {
|
||||
mode="range"
|
||||
startDate={range.startDate}
|
||||
endDate={range.endDate}
|
||||
onChange={(param) => setRange(param)}
|
||||
onChange={(param) => { setRange(param) }}
|
||||
styles={{
|
||||
selected: Styles.selectedDate,
|
||||
selected_label: Styles.cWhite,
|
||||
@@ -162,6 +202,13 @@ export default function ProjectAddTask() {
|
||||
{
|
||||
(error.endDate || error.startDate) && <Text style={[Styles.textInformation, Styles.cError, Styles.mt05]}>Tanggal tidak boleh kosong</Text>
|
||||
}
|
||||
<Pressable
|
||||
style={[Styles.btnTab, Styles.btnLainnya, dsbButton && Styles.btnDisabled]}
|
||||
disabled={dsbButton}
|
||||
onPress={() => { setModalDetail(true) }}
|
||||
>
|
||||
<Text style={[dsbButton ? Styles.cGray : Styles.cWhite]}>Detail</Text>
|
||||
</Pressable>
|
||||
</View>
|
||||
<InputForm
|
||||
label="Judul Tugas"
|
||||
@@ -179,6 +226,14 @@ export default function ProjectAddTask() {
|
||||
</View>
|
||||
</ScrollView>
|
||||
</KeyboardAvoidingView>
|
||||
<ModalAddDetailTugasProject
|
||||
isVisible={modalDetail}
|
||||
setVisible={setModalDetail}
|
||||
dataTanggal={dataDetail}
|
||||
onSubmit={(data) => {
|
||||
setDataDetail(data)
|
||||
}}
|
||||
/>
|
||||
</SafeAreaView>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -43,6 +43,8 @@ export default function EditProject() {
|
||||
setJudul(val)
|
||||
if (val == "" || val == "null") {
|
||||
setError(true)
|
||||
}else{
|
||||
setError(false)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
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 SectionReportProject from "@/components/project/sectionReportProject";
|
||||
import SectionTanggalTugasProject from "@/components/project/sectionTanggalTugas";
|
||||
import SectionCancel from "@/components/sectionCancel";
|
||||
import SectionProgress from "@/components/sectionProgress";
|
||||
@@ -111,8 +113,10 @@ export default function DetailProject() {
|
||||
data?.reason != null && data?.reason != "" && <SectionCancel text={data?.reason} />
|
||||
}
|
||||
<SectionProgress text={`Kemajuan Kegiatan ${progress}%`} progress={progress} />
|
||||
<SectionTanggalTugasProject status={data?.status} member={isMember} refreshing={refreshing}/>
|
||||
<SectionReportProject refreshing={refreshing} />
|
||||
<SectionTanggalTugasProject status={data?.status} member={isMember} refreshing={refreshing} />
|
||||
<SectionFile status={data?.status} member={isMember} refreshing={refreshing} />
|
||||
<SectionLink status={data?.status} member={isMember} refreshing={refreshing} />
|
||||
<SectionMember status={data?.status} refreshing={refreshing} />
|
||||
</View>
|
||||
</ScrollView>
|
||||
|
||||
128
app/(application)/project/[id]/report.tsx
Normal file
128
app/(application)/project/[id]/report.tsx
Normal file
@@ -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 (
|
||||
<SafeAreaView>
|
||||
<Stack.Screen
|
||||
options={{
|
||||
headerLeft: () => (
|
||||
<ButtonBackHeader
|
||||
onPress={() => {
|
||||
router.back();
|
||||
}}
|
||||
/>
|
||||
),
|
||||
headerTitle: "Laporan Kegiatan",
|
||||
headerTitleAlign: "center",
|
||||
headerRight: () => (
|
||||
<ButtonSaveHeader
|
||||
disable={disable || loading}
|
||||
category="update"
|
||||
onPress={() => { handleUpdate() }}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<ScrollView>
|
||||
<View style={[Styles.p15, Styles.mb100]}>
|
||||
<InputForm
|
||||
label="Laporan Kegiatan"
|
||||
type="default"
|
||||
placeholder="Laporan Kegiatan"
|
||||
required
|
||||
bg="white"
|
||||
value={laporan}
|
||||
onChange={(val) => { onValidation(val) }}
|
||||
error={error}
|
||||
errorText="Judul Kegiatan harus diisi"
|
||||
multiline
|
||||
/>
|
||||
</View>
|
||||
</ScrollView>
|
||||
</SafeAreaView>
|
||||
);
|
||||
}
|
||||
@@ -10,8 +10,10 @@ import ModalSelect from "@/components/modalSelect";
|
||||
import SectionListAddTask from "@/components/project/sectionListAddTask";
|
||||
import SelectForm from "@/components/selectForm";
|
||||
import Text from "@/components/Text";
|
||||
import { ConstEnv } from "@/constants/ConstEnv";
|
||||
import Styles from "@/constants/Styles";
|
||||
import { apiCreateProject } from "@/lib/api";
|
||||
import { setGroupChoose } from "@/lib/groupChoose";
|
||||
import { setMemberChoose } from "@/lib/memberChoose";
|
||||
import { setUpdateProject } from "@/lib/projectUpdate";
|
||||
import { setTaskCreate } from "@/lib/taskCreate";
|
||||
@@ -29,7 +31,7 @@ import Toast from "react-native-toast-message";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
|
||||
export default function CreateProject() {
|
||||
const [loading, setLoading] =useState(false)
|
||||
const [loading, setLoading] = useState(false)
|
||||
const { token, decryptToken } = useAuthSession();
|
||||
const [chooseGroup, setChooseGroup] = useState({ val: "", label: "" });
|
||||
const dispatch = useDispatch();
|
||||
@@ -40,7 +42,6 @@ export default function CreateProject() {
|
||||
const taskCreate = useSelector((state: any) => state.taskCreate);
|
||||
const update = useSelector((state: any) => state.projectUpdate)
|
||||
const entityUser = useSelector((state: any) => state.user);
|
||||
const userLogin = useSelector((state: any) => state.entities)
|
||||
const [fileForm, setFileForm] = useState<any[]>([])
|
||||
const [indexDelFile, setIndexDelFile] = useState<number>(0)
|
||||
const [disableBtn, setDisableBtn] = useState(true)
|
||||
@@ -56,10 +57,21 @@ export default function CreateProject() {
|
||||
member: false,
|
||||
});
|
||||
const [hitung, setHitung] = useState(0)
|
||||
let hitungRefresh = 0;
|
||||
|
||||
useEffect(() => {
|
||||
if (hitungRefresh == 0) {
|
||||
dispatch(setGroupChoose(''));
|
||||
dispatch(setTaskCreate([]));
|
||||
dispatch(setMemberChoose([]));
|
||||
}
|
||||
hitungRefresh++;
|
||||
}, []);
|
||||
|
||||
function validationForm(cat: string, val: any, label?: string) {
|
||||
if (cat == "group") {
|
||||
setChooseGroup({ val, label: String(label) });
|
||||
dispatch(setGroupChoose(val));
|
||||
dispatch(setMemberChoose([]));
|
||||
setDataForm({ ...dataForm, idGroup: val });
|
||||
if (val == "" || val == "null") {
|
||||
@@ -92,6 +104,7 @@ export default function CreateProject() {
|
||||
}
|
||||
|
||||
function handleBack() {
|
||||
dispatch(setGroupChoose(''));
|
||||
dispatch(setTaskCreate([]));
|
||||
dispatch(setMemberChoose([]));
|
||||
router.back();
|
||||
@@ -130,7 +143,7 @@ export default function CreateProject() {
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
Toast.show({ type: 'small', text1: 'Terjadi kesalahan', })
|
||||
}finally{
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
@@ -239,16 +252,12 @@ export default function CreateProject() {
|
||||
onPress={() => {
|
||||
if (entityUser.role == "supadmin" || entityUser.role == "developer") {
|
||||
if (chooseGroup.val != "") {
|
||||
setSelect(true);
|
||||
setValSelect("member");
|
||||
router.push(`/project/create/member`);
|
||||
} else {
|
||||
Toast.show({ type: 'small', text1: "Pilih Lembaga Desa terlebih dahulu", })
|
||||
}
|
||||
} else {
|
||||
validationForm('group', userLogin.idGroup, userLogin.group);
|
||||
setValChoose(userLogin.idGroup)
|
||||
setSelect(true);
|
||||
setValSelect("member");
|
||||
router.push(`/project/create/member`);
|
||||
}
|
||||
}}
|
||||
error={error.member}
|
||||
@@ -292,7 +301,7 @@ export default function CreateProject() {
|
||||
borderType="bottom"
|
||||
icon={
|
||||
<ImageUser
|
||||
src={`https://wibu-storage.wibudev.com/api/files/${item.img}`}
|
||||
src={`${ConstEnv.url_storage}/files/${item.img}`}
|
||||
size="sm"
|
||||
/>
|
||||
}
|
||||
|
||||
148
app/(application)/project/create/member.tsx
Normal file
148
app/(application)/project/create/member.tsx
Normal file
@@ -0,0 +1,148 @@
|
||||
import ButtonBackHeader from "@/components/buttonBackHeader";
|
||||
import ButtonSaveHeader from "@/components/buttonSaveHeader";
|
||||
import ImageUser from "@/components/imageNew";
|
||||
import ImageWithLabel from "@/components/imageWithLabel";
|
||||
import InputSearch from "@/components/inputSearch";
|
||||
import Text from "@/components/Text";
|
||||
import { ConstEnv } from "@/constants/ConstEnv";
|
||||
import Styles from "@/constants/Styles";
|
||||
import { apiGetUser } from "@/lib/api";
|
||||
import { setMemberChoose } from "@/lib/memberChoose";
|
||||
import { useAuthSession } from "@/providers/AuthProvider";
|
||||
import { AntDesign } from "@expo/vector-icons";
|
||||
import { router, Stack } from "expo-router";
|
||||
import { useEffect, useState } from "react";
|
||||
import { Pressable, SafeAreaView, ScrollView, View } from "react-native";
|
||||
import Toast from "react-native-toast-message";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
|
||||
type Props = {
|
||||
idUser: string,
|
||||
name: string,
|
||||
img: string
|
||||
}
|
||||
|
||||
export default function AddMemberCreateProject() {
|
||||
const dispatch = useDispatch()
|
||||
const { token, decryptToken } = useAuthSession()
|
||||
const [data, setData] = useState<Props[]>([])
|
||||
const [selectMember, setSelectMember] = useState<any[]>([])
|
||||
const [search, setSearch] = useState('')
|
||||
const entitiesMember = useSelector((state: any) => state.memberChoose)
|
||||
const entitiesGroup = useSelector((state: any) => state.groupChoose)
|
||||
const entityUser = useSelector((state: any) => state.user)
|
||||
const userLogin = useSelector((state: any) => state.entities)
|
||||
|
||||
async function handleLoadMember() {
|
||||
const hasil = await decryptToken(String(token?.current))
|
||||
let groupFix = userLogin.idGroup
|
||||
if (entityUser.role == 'supadmin' || entityUser.role == 'developer') {
|
||||
groupFix = entitiesGroup
|
||||
}
|
||||
|
||||
const responMemberDivision = await apiGetUser({ user: hasil, active: "true", search: search, group: groupFix })
|
||||
setData(responMemberDivision.data.filter((i: any) => i.idUserRole != 'supadmin'))
|
||||
if (entitiesMember.length > 0) {
|
||||
setSelectMember(entitiesMember)
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
handleLoadMember()
|
||||
}, [search]);
|
||||
|
||||
function onChoose(val: string, label: string, img?: string) {
|
||||
if (selectMember.some((i: any) => i.idUser == val)) {
|
||||
setSelectMember(selectMember.filter((i: any) => i.idUser != val))
|
||||
} else {
|
||||
setSelectMember([...selectMember, { idUser: val, name: label, img }])
|
||||
}
|
||||
}
|
||||
|
||||
async function handleAddMember() {
|
||||
try {
|
||||
dispatch(setMemberChoose(selectMember))
|
||||
router.back()
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
Toast.show({ type: 'small', text1: 'Gagal menambahkan anggota', })
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<SafeAreaView>
|
||||
<Stack.Screen
|
||||
options={{
|
||||
headerLeft: () => <ButtonBackHeader onPress={() => { router.back() }} />,
|
||||
headerTitle: 'Pilih Anggota',
|
||||
headerTitleAlign: 'center',
|
||||
headerRight: () => (
|
||||
<ButtonSaveHeader
|
||||
category="create"
|
||||
disable={selectMember.length > 0 ? false : true}
|
||||
onPress={() => {
|
||||
handleAddMember()
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
<View style={[Styles.p15]}>
|
||||
<InputSearch onChange={(val) => setSearch(val)} value={search} />
|
||||
|
||||
{
|
||||
selectMember.length > 0
|
||||
?
|
||||
<View>
|
||||
<ScrollView horizontal style={[Styles.mb10, Styles.pv10]}>
|
||||
{
|
||||
selectMember.map((item: any, index: any) => (
|
||||
<ImageWithLabel
|
||||
key={index}
|
||||
label={item.name}
|
||||
src={`${ConstEnv.url_storage}/files/${item.img}`}
|
||||
onClick={() => onChoose(item.idUser, item.name, item.img)}
|
||||
/>
|
||||
))
|
||||
}
|
||||
</ScrollView>
|
||||
</View>
|
||||
|
||||
:
|
||||
<Text style={[Styles.textDefault, Styles.cGray, Styles.pv05, { textAlign: 'center' }]}>Tidak ada member yang dipilih</Text>
|
||||
}
|
||||
<ScrollView>
|
||||
|
||||
{
|
||||
data.length > 0 ?
|
||||
data.map((item: any, index: any) => {
|
||||
return (
|
||||
<Pressable
|
||||
key={index}
|
||||
style={[Styles.itemSelectModal]}
|
||||
onPress={() => {
|
||||
onChoose(item.id, item.name, item.img)
|
||||
}}
|
||||
>
|
||||
<View style={[Styles.rowItemsCenter]}>
|
||||
<ImageUser src={`${ConstEnv.url_storage}/files/${item.img}`} border />
|
||||
<View style={[Styles.ml10]}>
|
||||
<Text style={[Styles.textDefault]}>{item.name}</Text>
|
||||
</View>
|
||||
</View>
|
||||
{
|
||||
selectMember.some((i: any) => i.idUser == item.id) && <AntDesign name="check" size={20} color={'black'} />
|
||||
}
|
||||
</Pressable>
|
||||
)
|
||||
}
|
||||
)
|
||||
:
|
||||
<Text style={[Styles.textDefault, { textAlign: 'center' }]}>Tidak ada data</Text>
|
||||
}
|
||||
</ScrollView>
|
||||
</View>
|
||||
</SafeAreaView>
|
||||
)
|
||||
}
|
||||
@@ -1,17 +1,22 @@
|
||||
import ButtonBackHeader from "@/components/buttonBackHeader";
|
||||
import ButtonSaveHeader from "@/components/buttonSaveHeader";
|
||||
import { InputForm } from "@/components/inputForm";
|
||||
import ModalAddDetailTugasProject from "@/components/project/modalAddDetailTugasProject";
|
||||
import Text from "@/components/Text";
|
||||
import Styles from "@/constants/Styles";
|
||||
import { formatDateOnly } from "@/lib/fun_formatDateOnly";
|
||||
import { getDatesInRange } from "@/lib/fun_getDatesInRange";
|
||||
import { setTaskCreate } from "@/lib/taskCreate";
|
||||
import dayjs from "dayjs";
|
||||
import { useHeaderHeight } from '@react-navigation/elements';
|
||||
import { router, Stack } from "expo-router";
|
||||
import 'intl';
|
||||
import 'intl/locale-data/jsonp/id';
|
||||
import moment from "moment";
|
||||
import { useEffect, useState } from "react";
|
||||
import {
|
||||
KeyboardAvoidingView,
|
||||
Platform,
|
||||
Pressable,
|
||||
SafeAreaView,
|
||||
ScrollView,
|
||||
View
|
||||
@@ -20,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 CreateProjectAddTask() {
|
||||
const headerHeight = useHeaderHeight();
|
||||
@@ -37,11 +41,12 @@ export default function CreateProjectAddTask() {
|
||||
})
|
||||
const [title, setTitle] = useState('');
|
||||
const taskCreate = useSelector((state: any) => state.taskCreate)
|
||||
const [dsbButton, setDsbButton] = useState(true)
|
||||
const [dataDetail, setDataDetail] = useState<any>([])
|
||||
const [modalDetail, setModalDetail] = useState(false)
|
||||
|
||||
const from = range.startDate
|
||||
? dayjs(range.startDate).format("DD-MM-YYYY")
|
||||
: "";
|
||||
const to = range.endDate ? dayjs(range.endDate).format("DD-MM-YYYY") : "";
|
||||
const from = formatDateOnly(range.startDate, "DD-MM-YYYY");
|
||||
const to = formatDateOnly(range.endDate, "DD-MM-YYYY");
|
||||
|
||||
function checkAll() {
|
||||
if (from == "" || to == "" || title == "" || title == "null" || error.startDate || error.endDate || error.title) {
|
||||
@@ -62,18 +67,45 @@ export default function CreateProjectAddTask() {
|
||||
}
|
||||
}
|
||||
|
||||
function checkButton() {
|
||||
if (range.startDate == null || range.endDate == null || range.startDate == undefined || range.endDate == undefined) {
|
||||
setDsbButton(true)
|
||||
setDataDetail([])
|
||||
} else {
|
||||
setDsbButton(false)
|
||||
const awal = formatDateOnly(range.startDate, "YYYY-MM-DD")
|
||||
const akhir = formatDateOnly(range.endDate, "YYYY-MM-DD")
|
||||
const datanya = getDatesInRange(awal, akhir)
|
||||
setDataDetail(datanya.map((item: any) => ({
|
||||
date: item,
|
||||
timeStart: null,
|
||||
timeEnd: null,
|
||||
})))
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
checkAll()
|
||||
}, [from, to, title, error])
|
||||
|
||||
useEffect(() => {
|
||||
checkButton()
|
||||
}, [range])
|
||||
|
||||
async function handleCreate() {
|
||||
try {
|
||||
const dataDetailFix = dataDetail.map((item: any) => ({
|
||||
date: moment(item.date, "DD-MM-YYYY").format("YYYY-MM-DD"),
|
||||
timeStart: item.timeStart,
|
||||
timeEnd: item.timeEnd,
|
||||
}))
|
||||
dispatch(setTaskCreate([...taskCreate, {
|
||||
title: title,
|
||||
dateStart: from,
|
||||
dateEnd: to,
|
||||
dateStartFix: dayjs(range.startDate).format("YYYY-MM-DD"),
|
||||
dateEndFix: dayjs(range.endDate).format("YYYY-MM-DD"),
|
||||
dateStartFix: formatDateOnly(range.startDate, "YYYY-MM-DD"),
|
||||
dateEndFix: formatDateOnly(range.endDate, "YYYY-MM-DD"),
|
||||
dataDetail: dataDetailFix
|
||||
}]))
|
||||
router.back();
|
||||
} catch (error) {
|
||||
@@ -151,6 +183,13 @@ export default function CreateProjectAddTask() {
|
||||
{
|
||||
(error.endDate || error.startDate) && <Text style={[Styles.textInformation, Styles.cError, Styles.mt05]}>Tanggal tidak boleh kosong</Text>
|
||||
}
|
||||
<Pressable
|
||||
style={[Styles.btnTab, Styles.btnLainnya, dsbButton && Styles.btnDisabled]}
|
||||
disabled={dsbButton}
|
||||
onPress={() => { setModalDetail(true) }}
|
||||
>
|
||||
<Text style={[dsbButton ? Styles.cGray : Styles.cWhite]}>Detail</Text>
|
||||
</Pressable>
|
||||
</View>
|
||||
<InputForm
|
||||
label="Judul Tugas"
|
||||
@@ -168,6 +207,14 @@ export default function CreateProjectAddTask() {
|
||||
</View>
|
||||
</ScrollView>
|
||||
</KeyboardAvoidingView>
|
||||
<ModalAddDetailTugasProject
|
||||
isVisible={modalDetail}
|
||||
setVisible={setModalDetail}
|
||||
dataTanggal={dataDetail}
|
||||
onSubmit={(data) => {
|
||||
setDataDetail(data)
|
||||
}}
|
||||
/>
|
||||
</SafeAreaView>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,18 +1,21 @@
|
||||
import ButtonBackHeader from "@/components/buttonBackHeader";
|
||||
import ButtonSaveHeader from "@/components/buttonSaveHeader";
|
||||
import { InputForm } from "@/components/inputForm";
|
||||
import ModalAddDetailTugasProject from "@/components/project/modalAddDetailTugasProject";
|
||||
import Text from "@/components/Text";
|
||||
import Styles from "@/constants/Styles";
|
||||
import { apiEditProjectTask, apiGetProjectTask } from "@/lib/api";
|
||||
import { formatDateOnly } from "@/lib/fun_formatDateOnly";
|
||||
import { getDatesInRange } from "@/lib/fun_getDatesInRange";
|
||||
import { setUpdateProject } from "@/lib/projectUpdate";
|
||||
import { useAuthSession } from "@/providers/AuthProvider";
|
||||
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 moment from "moment";
|
||||
import { useEffect, useState } from "react";
|
||||
import { KeyboardAvoidingView, Platform, SafeAreaView, ScrollView, View } from "react-native";
|
||||
import { KeyboardAvoidingView, Platform, Pressable, SafeAreaView, ScrollView, View } from "react-native";
|
||||
import Toast from "react-native-toast-message";
|
||||
import DateTimePicker, { DateType } from "react-native-ui-datepicker";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
@@ -29,6 +32,9 @@ export default function UpdateProjectTask() {
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [disableBtn, setDisableBtn] = useState(false)
|
||||
const [loadingSubmit, setLoadingSubmit] = useState(false)
|
||||
const [dataDetail, setDataDetail] = useState<any>([])
|
||||
const [modalDetail, setModalDetail] = useState(false)
|
||||
const [dsbButton, setDsbButton] = useState(true)
|
||||
|
||||
const [title, setTitle] = useState('')
|
||||
const [error, setError] = useState({
|
||||
@@ -37,10 +43,8 @@ export default function UpdateProjectTask() {
|
||||
title: false,
|
||||
})
|
||||
|
||||
const from = range.startDate
|
||||
? dayjs(range.startDate).format("DD-MM-YYYY")
|
||||
: '';
|
||||
const to = range.endDate ? dayjs(range.endDate).format("DD-MM-YYYY") : '';
|
||||
const from = formatDateOnly(range.startDate);
|
||||
const to = formatDateOnly(range.endDate);
|
||||
|
||||
async function handleLoad() {
|
||||
try {
|
||||
@@ -57,6 +61,23 @@ export default function UpdateProjectTask() {
|
||||
})
|
||||
setMonth(new Date(response.data.dateStart).getMonth())
|
||||
setYear(new Date(response.data.dateStart).getFullYear())
|
||||
|
||||
const response2 = await apiGetProjectTask({
|
||||
user: hasil,
|
||||
id: detail,
|
||||
cat: "detailTask"
|
||||
});
|
||||
if (response2.data.length == 0) {
|
||||
const datanya = getDatesInRange(response.data.dateStart, response.data.dateEnd)
|
||||
setDataDetail(datanya.map((item: any) => ({
|
||||
date: item,
|
||||
timeStart: null,
|
||||
timeEnd: null,
|
||||
})))
|
||||
} else {
|
||||
setDataDetail(response2.data)
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
} finally {
|
||||
@@ -71,8 +92,22 @@ export default function UpdateProjectTask() {
|
||||
async function handleEdit() {
|
||||
try {
|
||||
setLoadingSubmit(true)
|
||||
const dataDetailFix = dataDetail.map((item: any) => ({
|
||||
date: moment(item.date, "DD-MM-YYYY").format("YYYY-MM-DD"),
|
||||
timeStart: item.timeStart,
|
||||
timeEnd: item.timeEnd,
|
||||
}))
|
||||
const hasil = await decryptToken(String(token?.current));
|
||||
const response = await apiEditProjectTask({ data: { title, dateStart: dayjs(range.startDate).format("YYYY-MM-DD"), dateEnd: dayjs(range.endDate).format("YYYY-MM-DD"), user: hasil }, id: detail });
|
||||
const response = await apiEditProjectTask({
|
||||
data: {
|
||||
title,
|
||||
dateStart: formatDateOnly(range.startDate, "YYYY-MM-DD"),
|
||||
dateEnd: formatDateOnly(range.endDate, "YYYY-MM-DD"),
|
||||
user: hasil,
|
||||
dataDetail: dataDetailFix
|
||||
},
|
||||
id: detail
|
||||
});
|
||||
if (response.success) {
|
||||
dispatch(setUpdateProject({ ...update, task: !update.task, progress: !update.progress }))
|
||||
Toast.show({ type: 'small', text1: 'Berhasil mengubah data', })
|
||||
@@ -107,10 +142,32 @@ export default function UpdateProjectTask() {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function checkButton() {
|
||||
if (range.startDate == null || range.endDate == null || range.startDate == undefined || range.endDate == undefined) {
|
||||
setDsbButton(true)
|
||||
setDataDetail([])
|
||||
} else {
|
||||
setDsbButton(false)
|
||||
const awal = formatDateOnly(range.startDate, "YYYY-MM-DD")
|
||||
const akhir = formatDateOnly(range.endDate, "YYYY-MM-DD")
|
||||
const datanya = getDatesInRange(awal, akhir)
|
||||
setDataDetail(datanya.map((item: any) => ({
|
||||
date: item,
|
||||
timeStart: null,
|
||||
timeEnd: null,
|
||||
})))
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
checkAll()
|
||||
}, [from, to, title, error])
|
||||
|
||||
useEffect(() => {
|
||||
checkButton()
|
||||
}, [range])
|
||||
|
||||
return (
|
||||
<SafeAreaView>
|
||||
<Stack.Screen
|
||||
@@ -176,6 +233,13 @@ export default function UpdateProjectTask() {
|
||||
{
|
||||
(error.endDate || error.startDate) && <Text style={[Styles.textInformation, Styles.cError, Styles.mt05]}>Tanggal tidak boleh kosong</Text>
|
||||
}
|
||||
<Pressable
|
||||
style={[Styles.btnTab, Styles.btnLainnya, dsbButton && Styles.btnDisabled]}
|
||||
disabled={dsbButton}
|
||||
onPress={() => { setModalDetail(true) }}
|
||||
>
|
||||
<Text style={[dsbButton ? Styles.cGray : Styles.cWhite]}>Detail</Text>
|
||||
</Pressable>
|
||||
</View>
|
||||
<InputForm
|
||||
label="Judul Tugas"
|
||||
@@ -193,6 +257,15 @@ export default function UpdateProjectTask() {
|
||||
</View>
|
||||
</ScrollView>
|
||||
</KeyboardAvoidingView>
|
||||
|
||||
<ModalAddDetailTugasProject
|
||||
isVisible={modalDetail}
|
||||
setVisible={setModalDetail}
|
||||
dataTanggal={dataDetail}
|
||||
onSubmit={(data) => {
|
||||
setDataDetail(data)
|
||||
}}
|
||||
/>
|
||||
</SafeAreaView>
|
||||
)
|
||||
}
|
||||
@@ -3,6 +3,7 @@ import ButtonBackHeader from "@/components/buttonBackHeader";
|
||||
import InputSearch from "@/components/inputSearch";
|
||||
import Text from '@/components/Text';
|
||||
import { ColorsStatus } from "@/constants/ColorsStatus";
|
||||
import { ConstEnv } from "@/constants/ConstEnv";
|
||||
import Styles from "@/constants/Styles";
|
||||
import { apiGetSearch } from "@/lib/api";
|
||||
import { useAuthSession } from "@/providers/AuthProvider";
|
||||
@@ -92,7 +93,7 @@ export default function Search() {
|
||||
<BorderBottomItem
|
||||
borderType="bottom"
|
||||
icon={<Image
|
||||
source={{ uri: `https://wibu-storage.wibudev.com/api/files/${item.img}` }}
|
||||
source={{ uri: `${ConstEnv.url_storage}/files/${item.img}` }}
|
||||
style={[Styles.userProfileSmall]}
|
||||
/>}
|
||||
title={item.name}
|
||||
|
||||
@@ -4,9 +4,9 @@ import { Stack } from 'expo-router';
|
||||
import * as SplashScreen from 'expo-splash-screen';
|
||||
import { StatusBar } from 'expo-status-bar';
|
||||
import { useEffect } from 'react';
|
||||
import 'react-native-reanimated';
|
||||
import { NotifierWrapper } from 'react-native-notifier';
|
||||
import { GestureHandlerRootView } from 'react-native-gesture-handler';
|
||||
import { NotifierWrapper } from 'react-native-notifier';
|
||||
import 'react-native-reanimated';
|
||||
|
||||
// Prevent the splash screen from auto-hiding before asset loading is complete.
|
||||
SplashScreen.preventAutoHideAsync();
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { ButtonForm } from "@/components/buttonForm";
|
||||
import Text from '@/components/Text';
|
||||
import { ConstEnv } from "@/constants/ConstEnv";
|
||||
import Styles from "@/constants/Styles";
|
||||
import { useAuthSession } from "@/providers/AuthProvider";
|
||||
import CryptoES from "crypto-es";
|
||||
@@ -19,10 +20,7 @@ export default function Index() {
|
||||
const login = (): void => {
|
||||
const random: string = 'contohLoginMobileDarmasaba';
|
||||
var mytexttoEncryption = "contohLoginMobileDarmasaba"
|
||||
const encrypted = CryptoES.AES.encrypt(mytexttoEncryption, "your password").toString();
|
||||
// var C = require("crypto-js");
|
||||
// var Decrypted = C.AES.decrypt(encrypted, "your password");
|
||||
// var result = Decrypted.toString(C.enc.Utf8);
|
||||
const encrypted = CryptoES.AES.encrypt(mytexttoEncryption, ConstEnv.pass_encrypt).toString();
|
||||
signIn(encrypted);
|
||||
}
|
||||
return (
|
||||
|
||||
@@ -44,7 +44,7 @@ export default function ViewLogin({ onValidate }: Props) {
|
||||
return (
|
||||
<SafeAreaView>
|
||||
<ToastCustom />
|
||||
<View style={Styles.p20}>
|
||||
<View style={[Styles.p20, Styles.h100]}>
|
||||
<View style={{ alignItems: "center", marginVertical: 50 }}>
|
||||
<Image
|
||||
source={require("../../assets/images/splash-icon.png")}
|
||||
|
||||
@@ -35,7 +35,7 @@ export default function ViewVerification({ phone, otp }: Props) {
|
||||
if (value === otpFix.toString()) {
|
||||
login()
|
||||
} else {
|
||||
return Toast.show({ type: 'error', text1: 'Kode OTP tidak sesuai' });
|
||||
return Toast.show({ type: 'small', text1: 'Kode OTP tidak sesuai' });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ export default function BorderBottomItem({ title, subtitle, icon, desc, onPress,
|
||||
<View style={[Styles.rowItemsCenter]}>
|
||||
{icon}
|
||||
<View style={[Styles.rowSpaceBetween, width ? { width: lebar } : { width: '88%' }]}>
|
||||
<View style={[Styles.ml10, rightTopInfo ? { width: lebar } : { width: '90%' },]}>
|
||||
<View style={[Styles.ml10, rightTopInfo ? { width: '70%' } : { width: '90%' }]}>
|
||||
<Text style={[titleWeight == 'normal' ? Styles.textDefault : Styles.textDefaultSemiBold, { color: textColorFix }]} numberOfLines={1} ellipsizeMode='tail'>{title}</Text>
|
||||
{
|
||||
subtitle &&
|
||||
|
||||
@@ -1,21 +1,22 @@
|
||||
import Styles from "@/constants/Styles";
|
||||
import { View } from "react-native";
|
||||
import { Pressable, View } from "react-native";
|
||||
import Text from "../Text";
|
||||
|
||||
type Props = {
|
||||
text: string;
|
||||
isSelected: boolean;
|
||||
isSign: boolean;
|
||||
onPress?: () => void;
|
||||
}
|
||||
|
||||
|
||||
export default function ItemDateCalendar({ text, isSelected, isSign }: Props) {
|
||||
export default function ItemDateCalendar({ text, isSelected, isSign, onPress }: Props) {
|
||||
return (
|
||||
<>
|
||||
<View style={{ alignItems: 'center' }}>
|
||||
<Pressable style={{ alignItems: 'center' }} onPress={onPress}>
|
||||
<Text style={[isSelected ? Styles.cWhite : Styles.cBlack]}>{text}</Text>
|
||||
<View style={[Styles.signDate, { backgroundColor: isSign ? 'red' : 'transparent' }]}></View>
|
||||
</View>
|
||||
</Pressable>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
import { ConstEnv } from "@/constants/ConstEnv";
|
||||
import Styles from "@/constants/Styles";
|
||||
import { apiGetDivisionOneFeature } from "@/lib/api";
|
||||
import { useAuthSession } from "@/providers/AuthProvider";
|
||||
@@ -8,11 +9,11 @@ import { useLocalSearchParams } from "expo-router";
|
||||
import * as Sharing from 'expo-sharing';
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { Alert, Dimensions, Platform, Pressable, View } from "react-native";
|
||||
import Text from "../Text";
|
||||
import * as mime from 'react-native-mime-types';
|
||||
import { ICarouselInstance } from "react-native-reanimated-carousel";
|
||||
import Skeleton from "../skeleton";
|
||||
import ModalLoading from "../modalLoading";
|
||||
import Skeleton from "../skeleton";
|
||||
import Text from "../Text";
|
||||
|
||||
type Props = {
|
||||
id: string
|
||||
@@ -52,7 +53,7 @@ export default function FileDivisionDetail() {
|
||||
|
||||
const openFile = (item: Props) => {
|
||||
if (Platform.OS == 'android') setLoadingOpen(true)
|
||||
let remoteUrl = 'https://wibu-storage.wibudev.com/api/files/' + item.idStorage;
|
||||
let remoteUrl = ConstEnv.url_storage + '/files/' + item.idStorage;
|
||||
const fileName = item.name + '.' + item.extension;
|
||||
let localPath = `${FileSystem.documentDirectory}/${fileName}`;
|
||||
const mimeType = mime.lookup(fileName)
|
||||
|
||||
@@ -2,12 +2,12 @@ import Styles from "@/constants/Styles";
|
||||
import { apiGetDivisionOneFeature } from "@/lib/api";
|
||||
import { useAuthSession } from "@/providers/AuthProvider";
|
||||
import { Feather } from "@expo/vector-icons";
|
||||
import { useLocalSearchParams } from "expo-router";
|
||||
import { router, useLocalSearchParams } from "expo-router";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { Dimensions, View } from "react-native";
|
||||
import Text from "../Text";
|
||||
import Carousel, { ICarouselInstance } from "react-native-reanimated-carousel";
|
||||
import { Dimensions, Pressable, View } from "react-native";
|
||||
import { ICarouselInstance } from "react-native-reanimated-carousel";
|
||||
import Skeleton from "../skeleton";
|
||||
import Text from "../Text";
|
||||
|
||||
type Props = {
|
||||
id: string
|
||||
@@ -46,34 +46,42 @@ export default function TaskDivisionDetail() {
|
||||
return (
|
||||
<View>
|
||||
<Text style={[Styles.textDefaultSemiBold, Styles.mb05]}>Tugas Hari Ini</Text>
|
||||
|
||||
{
|
||||
loading ?
|
||||
<Skeleton width={100} widthType="percent" height={60} borderRadius={10} />
|
||||
:
|
||||
data.length > 0 ?
|
||||
<Carousel
|
||||
ref={ref}
|
||||
style={{ width: "100%" }}
|
||||
width={width * 0.8}
|
||||
height={100}
|
||||
data={data}
|
||||
loop={true}
|
||||
autoPlay={false}
|
||||
autoPlayReverse={false}
|
||||
pagingEnabled={true}
|
||||
snapEnabled={true}
|
||||
vertical={false}
|
||||
renderItem={({ index }) => (
|
||||
<View style={[Styles.wrapPaper, { width: '95%' }]}>
|
||||
<Text style={[Styles.textDefaultSemiBold]} numberOfLines={1} ellipsizeMode="tail">{data[index].title} - {data[index].projectTitle}</Text>
|
||||
<View style={[Styles.rowItemsCenter, Styles.mt10]}>
|
||||
<Feather name="clock" size={18} color="grey" style={Styles.mr05} />
|
||||
<Text style={[Styles.textInformation]} numberOfLines={1} ellipsizeMode="tail">{data[index].dateStart} - {data[index].dateEnd}</Text>
|
||||
</View>
|
||||
data.map((item, index) => (
|
||||
<Pressable key={index} style={[Styles.wrapPaper]} onPress={() => { router.push(`/division/${id}/task/${item.idProject}`) }}>
|
||||
<Text style={[Styles.textDefaultSemiBold]} numberOfLines={1} ellipsizeMode="tail">{item.title} - {item.projectTitle}</Text>
|
||||
<View style={[Styles.rowItemsCenter, Styles.mt10]}>
|
||||
<Feather name="clock" size={18} color="grey" style={Styles.mr05} />
|
||||
<Text style={[Styles.textInformation]} numberOfLines={1} ellipsizeMode="tail">{item.dateStart} - {item.dateEnd}</Text>
|
||||
</View>
|
||||
)}
|
||||
/>
|
||||
</Pressable>
|
||||
))
|
||||
// <Carousel
|
||||
// ref={ref}
|
||||
// style={{ width: "100%" }}
|
||||
// width={width * 0.8}
|
||||
// height={100}
|
||||
// data={data}
|
||||
// loop={true}
|
||||
// autoPlay={false}
|
||||
// autoPlayReverse={false}
|
||||
// pagingEnabled={true}
|
||||
// snapEnabled={true}
|
||||
// vertical={false}
|
||||
// renderItem={({ index }) => (
|
||||
// <View style={[Styles.wrapPaper, { width: '95%' }]}>
|
||||
// <Text style={[Styles.textDefaultSemiBold]} numberOfLines={1} ellipsizeMode="tail">{data[index].title} - {data[index].projectTitle}</Text>
|
||||
// <View style={[Styles.rowItemsCenter, Styles.mt10]}>
|
||||
// <Feather name="clock" size={18} color="grey" style={Styles.mr05} />
|
||||
// <Text style={[Styles.textInformation]} numberOfLines={1} ellipsizeMode="tail">{data[index].dateStart} - {data[index].dateEnd}</Text>
|
||||
// </View>
|
||||
// </View>
|
||||
// )}
|
||||
// />
|
||||
:
|
||||
<Text style={[Styles.textDefault, Styles.cGray, { textAlign: 'center' }]}>Tidak ada tugas</Text>
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { ConstEnv } from "@/constants/ConstEnv";
|
||||
import Styles from "@/constants/Styles";
|
||||
import { apiGetBanner, apiGetProfile } from "@/lib/api";
|
||||
import { setEntities } from "@/lib/bannerSlice";
|
||||
@@ -50,7 +51,7 @@ export default function CaraouselHome() {
|
||||
onProgressChange={progress}
|
||||
renderItem={({ index }) => (
|
||||
<Image
|
||||
source={{ uri: `https://wibu-storage.wibudev.com/api/files/${entities[index].image}` }}
|
||||
source={{ uri: `${ConstEnv.url_storage}/files/${entities[index].image}` }}
|
||||
style={[Styles.caraoselContent]}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { ColorsStatus } from "@/constants/ColorsStatus";
|
||||
import Styles from "@/constants/Styles";
|
||||
import { stringToDate, stringToDateTime } from "@/lib/fun_stringToDate";
|
||||
import DateTimePicker from "@react-native-community/datetimepicker";
|
||||
import dayjs from "dayjs";
|
||||
import { useState } from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { Platform, Pressable, View } from "react-native";
|
||||
import Text from "./Text";
|
||||
import ModalFloat from "./modalFloat";
|
||||
@@ -26,20 +27,50 @@ 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 onChangeDate = (type: string, selectedDate: any) => {
|
||||
if (type === "set") {
|
||||
let formatted = ""
|
||||
if (mode == "date") {
|
||||
onChange(dayjs(selectedDate).format("DD-MM-YYYY"))
|
||||
formatted = dayjs(selectedDate).format("DD-MM-YYYY")
|
||||
} else if (mode == "time") {
|
||||
onChange(dayjs(selectedDate).format("HH:mm"))
|
||||
formatted = dayjs(selectedDate).format("HH:mm")
|
||||
}
|
||||
|
||||
setValueFirst(formatted)
|
||||
|
||||
if (Platform.OS == "android") {
|
||||
onChange(formatted)
|
||||
setModal(false)
|
||||
}
|
||||
setModal(false)
|
||||
} else {
|
||||
setModal(false);
|
||||
}
|
||||
};
|
||||
|
||||
function onSetValue() {
|
||||
onChange(valueFirst)
|
||||
setModal(false)
|
||||
}
|
||||
|
||||
function changeValue() {
|
||||
if (value) {
|
||||
let valDate = new Date()
|
||||
if (mode == "date") {
|
||||
valDate = stringToDate(value)
|
||||
} else if (mode == "time") {
|
||||
valDate = stringToDateTime("", value)
|
||||
}
|
||||
setValueFix(valDate)
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (modal) changeValue()
|
||||
}, [value, modal])
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
<View style={[Styles.mb10]}>
|
||||
@@ -63,23 +94,24 @@ export function InputDate({ label, value, placeholder, onChange, info, disable,
|
||||
<ModalFloat
|
||||
isVisible={modal}
|
||||
setVisible={setModal}
|
||||
onSubmit={() => { }}
|
||||
buttonHide
|
||||
disableSubmit
|
||||
onSubmit={() => { onSetValue() }}
|
||||
title={mode == "date" ? "Pilih Tanggal" : mode == "time" ? "Pilih Jam" : "Pilih Tanggal & Jam"}>
|
||||
<DateTimePicker
|
||||
value={new Date()}
|
||||
value={valueFix}
|
||||
mode={mode}
|
||||
display="spinner"
|
||||
onChange={(event, date) => { onChangeDate(event.type, date) }}
|
||||
onChange={(event, date) => {
|
||||
onChangeDate(event.type, date)
|
||||
}}
|
||||
onTouchCancel={() => setModal(false)}
|
||||
|
||||
/>
|
||||
</ModalFloat>
|
||||
)
|
||||
) : (
|
||||
modal && (
|
||||
<DateTimePicker
|
||||
value={new Date()}
|
||||
value={valueFix}
|
||||
mode={mode}
|
||||
display="inline"
|
||||
onChange={(event, date) => { onChangeDate(event.type, date) }}
|
||||
|
||||
@@ -8,7 +8,7 @@ type Props = {
|
||||
setVisible: (value: boolean) => void
|
||||
title?: string
|
||||
children: React.ReactNode
|
||||
onSubmit: () => void
|
||||
onSubmit?: () => void
|
||||
disableSubmit?: boolean
|
||||
buttonHide?: boolean
|
||||
}
|
||||
@@ -33,10 +33,10 @@ export default function ModalFloat({ isVisible, setVisible, title, children, onS
|
||||
!buttonHide && (
|
||||
<View style={[Styles.rowItemsCenter, { justifyContent: 'flex-end' }]}>
|
||||
<Pressable style={[Styles.ph15, Styles.pv05, Styles.round10, Styles.mr10]} onPress={() => { setVisible(false) }}>
|
||||
<Text style={[Styles.textDefault]}>Batal</Text>
|
||||
<Text style={[Styles.textDefaultSemiBold]}>Batal</Text>
|
||||
</Pressable>
|
||||
<Pressable style={[Styles.ph15, Styles.pv05, Styles.round10]} onPress={onSubmit} disabled={disableSubmit}>
|
||||
<Text style={[Styles.textDefault, disableSubmit && Styles.cGray]}>Simpan</Text>
|
||||
<Text style={[Styles.textDefaultSemiBold, disableSubmit && Styles.cGray]}>Simpan</Text>
|
||||
</Pressable>
|
||||
</View>
|
||||
)
|
||||
|
||||
@@ -13,7 +13,9 @@ export default function ModalLoading({ isVisible, setVisible }: Props) {
|
||||
animationOut={"slideOutDown"}
|
||||
isVisible={isVisible}
|
||||
hideModalContentWhileAnimating={true}
|
||||
onBackdropPress={() => { setVisible(false) }}
|
||||
onBackdropPress={() => {
|
||||
// setVisible(false)
|
||||
}}
|
||||
>
|
||||
<ActivityIndicator size="large" />
|
||||
</Modal>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { ConstEnv } from "@/constants/ConstEnv"
|
||||
import { valueGender } from "@/constants/Gender"
|
||||
import { valueRoleUser } from "@/constants/RoleUser"
|
||||
import Styles from "@/constants/Styles"
|
||||
@@ -140,7 +141,7 @@ export default function ModalSelect({ open, close, title, category, idParent, on
|
||||
<ImageWithLabel
|
||||
key={index}
|
||||
label={item.name}
|
||||
src={`https://wibu-storage.wibudev.com/api/files/${item.img}`}
|
||||
src={`${ConstEnv.url_storage}/files/${item.img}`}
|
||||
onClick={() => onChoose(item.idUser, item.name, item.img)}
|
||||
/>
|
||||
))
|
||||
@@ -164,7 +165,7 @@ export default function ModalSelect({ open, close, title, category, idParent, on
|
||||
category == 'member'
|
||||
?
|
||||
<View style={[Styles.rowItemsCenter]}>
|
||||
<ImageUser src={`https://wibu-storage.wibudev.com/api/files/${item.img}`} border />
|
||||
<ImageUser src={`${ConstEnv.url_storage}/files/${item.img}`} border />
|
||||
<Text style={[Styles.textDefault, Styles.ml10]}>{item.name}</Text>
|
||||
</View>
|
||||
:
|
||||
|
||||
@@ -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,10 +47,27 @@ 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 (
|
||||
<>
|
||||
<ButtonMenuHeader onPress={() => { setVisible(true) }} />
|
||||
<DrawerBottom animation="slide" isVisible={isVisible} setVisible={setVisible} title="Menu">
|
||||
<DrawerBottom animation="slide" isVisible={isVisible} setVisible={setVisible} title="Menu" height={30}>
|
||||
<View style={Styles.rowItemsCenter}>
|
||||
<MenuItemRow
|
||||
icon={<AntDesign name="pluscircle" color="black" size={25} />}
|
||||
@@ -68,33 +89,59 @@ export default function HeaderRightProjectDetail({ id, status }: Props) {
|
||||
}}
|
||||
disabled={status == 3}
|
||||
/>
|
||||
<MenuItemRow
|
||||
icon={<Feather name="link" color="black" size={25} />}
|
||||
title="Tambah Link"
|
||||
onPress={() => {
|
||||
if (status == 3) return
|
||||
setVisible(false)
|
||||
setTimeout(() => {
|
||||
setAddLink(true)
|
||||
}, 600)
|
||||
}}
|
||||
disabled={status == 3}
|
||||
/>
|
||||
</View>
|
||||
<View style={[Styles.rowItemsCenter, Styles.mt15]}>
|
||||
<MenuItemRow
|
||||
icon={<MaterialCommunityIcons name="file-document" color="black" size={25} />}
|
||||
title="Laporan"
|
||||
onPress={() => {
|
||||
if (status == 3) return
|
||||
setVisible(false)
|
||||
router.push(`/project/${id}/report`)
|
||||
}}
|
||||
disabled={status == 3}
|
||||
/>
|
||||
{
|
||||
entityUser.role != "user" && entityUser.role != "coadmin" &&
|
||||
<MenuItemRow
|
||||
icon={<MaterialIcons name="groups" color="black" size={25} />}
|
||||
title="Tambah Anggota"
|
||||
onPress={() => {
|
||||
if (status == 3) return
|
||||
setVisible(false)
|
||||
router.push(`/project/${id}/add-member`)
|
||||
}}
|
||||
disabled={status == 3}
|
||||
/>
|
||||
<>
|
||||
<MenuItemRow
|
||||
icon={<MaterialIcons name="groups" color="black" size={25} />}
|
||||
title="Tambah Anggota"
|
||||
onPress={() => {
|
||||
if (status == 3) return
|
||||
setVisible(false)
|
||||
router.push(`/project/${id}/add-member`)
|
||||
}}
|
||||
disabled={status == 3}
|
||||
/>
|
||||
<MenuItemRow
|
||||
icon={<MaterialCommunityIcons name="pencil-outline" color="black" size={25} />}
|
||||
title="Edit"
|
||||
onPress={() => {
|
||||
if (status == 3) return
|
||||
setVisible(false)
|
||||
router.push(`/project/${id}/edit`)
|
||||
}}
|
||||
disabled={status == 3}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
</View>
|
||||
{
|
||||
entityUser.role != "user" && entityUser.role != "coadmin" &&
|
||||
<View style={[Styles.rowItemsCenter, Styles.mt15]}>
|
||||
<MenuItemRow
|
||||
icon={<MaterialCommunityIcons name="pencil-outline" color="black" size={25} />}
|
||||
title="Edit"
|
||||
onPress={() => {
|
||||
if (status == 3) return
|
||||
setVisible(false)
|
||||
router.push(`/project/${id}/edit`)
|
||||
}}
|
||||
disabled={status == 3}
|
||||
/>
|
||||
{
|
||||
status == 3
|
||||
?
|
||||
@@ -123,6 +170,23 @@ export default function HeaderRightProjectDetail({ id, status }: Props) {
|
||||
</View>
|
||||
}
|
||||
</DrawerBottom>
|
||||
|
||||
<ModalFloat
|
||||
title="Tambah Link"
|
||||
isVisible={isAddLink}
|
||||
setVisible={() => { setAddLink(false) }}
|
||||
onSubmit={() => { handleAddLink() }}
|
||||
disableSubmit={link == ""}
|
||||
>
|
||||
<View>
|
||||
<InputForm
|
||||
type="default"
|
||||
placeholder="Masukkan link"
|
||||
value={link}
|
||||
onChange={(text) => { setLink(text) }}
|
||||
/>
|
||||
</View>
|
||||
</ModalFloat>
|
||||
</>
|
||||
)
|
||||
}
|
||||
135
components/project/modalAddDetailTugasProject.tsx
Normal file
135
components/project/modalAddDetailTugasProject.tsx
Normal file
@@ -0,0 +1,135 @@
|
||||
import Styles from "@/constants/Styles";
|
||||
import { stringToDateTime } from "@/lib/fun_stringToDate";
|
||||
import { useEffect, useState } from "react";
|
||||
import { Dimensions, View, VirtualizedList } from "react-native";
|
||||
import { InputDate } from "../inputDate";
|
||||
import ModalFloat from "../modalFloat";
|
||||
import Text from "../Text";
|
||||
|
||||
interface Props {
|
||||
date: string;
|
||||
timeStart: string;
|
||||
timeEnd: string;
|
||||
}
|
||||
|
||||
export default function ModalAddDetailTugasProject({ isVisible, setVisible, dataTanggal, onSubmit }: { isVisible: boolean, setVisible: (value: boolean) => void, dataTanggal: Props[], onSubmit: (data: Props[]) => void }) {
|
||||
const [data, setData] = useState<Props[]>(dataTanggal)
|
||||
const tinggiScreen = Dimensions.get("window").height;
|
||||
const tinggiFix = tinggiScreen * 70 / 100;
|
||||
const [error, setError] = useState<any>([])
|
||||
|
||||
useEffect(() => {
|
||||
if (isVisible) {
|
||||
setData(dataTanggal)
|
||||
setError([])
|
||||
}
|
||||
}, [isVisible, dataTanggal])
|
||||
|
||||
|
||||
const getItem = (_data: unknown, index: number): Props => ({
|
||||
date: data[index].date,
|
||||
timeStart: data[index].timeStart,
|
||||
timeEnd: data[index].timeEnd,
|
||||
})
|
||||
|
||||
function settingError(date: string, cat: 'timeStart' | 'timeEnd', val: boolean) {
|
||||
const ada = error.find((item: any) => item.date == date)
|
||||
if (ada) {
|
||||
setError(error.map((item: any) => {
|
||||
if (item.date == date) {
|
||||
return { ...item, [cat]: val }
|
||||
}
|
||||
return item
|
||||
}))
|
||||
} else {
|
||||
setError([...error, { date, [cat]: val }])
|
||||
}
|
||||
}
|
||||
|
||||
function validationForm(cat: "timeStart" | "timeEnd", val: string, date: string) {
|
||||
if (cat == "timeEnd") {
|
||||
const start = stringToDateTime("", String(data.find((item) => item.date == date)?.timeStart))
|
||||
const end = stringToDateTime("", val)
|
||||
const timestampAwal = start.getTime()
|
||||
const timestampAkhir = end.getTime()
|
||||
if (val == "" || val == null || timestampAwal > timestampAkhir) {
|
||||
settingError(date, "timeEnd", true)
|
||||
} else {
|
||||
settingError(date, "timeEnd", false)
|
||||
}
|
||||
} else {
|
||||
const end = stringToDateTime("", String(data.find((item) => item.date == date)?.timeEnd))
|
||||
const start = stringToDateTime("", val)
|
||||
const timestampAwal = start.getTime()
|
||||
const timestampAkhir = end.getTime()
|
||||
|
||||
if (val == "" || val == null || timestampAwal > timestampAkhir) {
|
||||
settingError(date, "timeEnd", true)
|
||||
} else {
|
||||
settingError(date, "timeEnd", false)
|
||||
}
|
||||
}
|
||||
|
||||
setData(data.map((item) => {
|
||||
if (item.date == date) {
|
||||
return { ...item, [cat]: val }
|
||||
}
|
||||
return item
|
||||
}))
|
||||
}
|
||||
|
||||
return (
|
||||
<ModalFloat
|
||||
title="Detail Tanggal dan Waktu Tugas"
|
||||
isVisible={isVisible}
|
||||
setVisible={setVisible}
|
||||
disableSubmit={Object.values(error).some((val: any) => val.timeEnd == true || val.timeStart == true)}
|
||||
onSubmit={() => {
|
||||
onSubmit(data)
|
||||
setVisible(false)
|
||||
}}
|
||||
>
|
||||
<View style={[{ height: tinggiFix }]} >
|
||||
<VirtualizedList
|
||||
data={data}
|
||||
getItemCount={() => data.length}
|
||||
getItem={getItem}
|
||||
renderItem={({ item, index }: { item: Props, index: number }) => {
|
||||
return (
|
||||
<View key={index} style={[Styles.borderBottom, Styles.pv05]}>
|
||||
<Text style={[Styles.textDefaultSemiBold]}>{item.date}</Text>
|
||||
<View style={[Styles.rowSpaceBetween]}>
|
||||
<View style={[{ width: "48%" }]}>
|
||||
<InputDate
|
||||
mode="time"
|
||||
onChange={(val) => { validationForm("timeStart", val, item.date) }}
|
||||
value={item.timeStart}
|
||||
label="Waktu Awal"
|
||||
placeholder="--:--"
|
||||
error={error.find((error: any) => error.date == item.date)?.timeStart}
|
||||
errorText="Waktu awal tidak valid"
|
||||
/>
|
||||
</View>
|
||||
<View style={[{ width: "48%" }]}>
|
||||
<InputDate
|
||||
onChange={(val) => { validationForm("timeEnd", val, item.date) }}
|
||||
mode="time"
|
||||
value={item.timeEnd}
|
||||
label="Waktu Akhir"
|
||||
placeholder="--:--"
|
||||
error={error.find((error: any) => error.date == item.date)?.timeEnd}
|
||||
errorText="Waktu akhir harus lebih dari waktu awal"
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
)
|
||||
}}
|
||||
keyExtractor={(item, index) => String(index)}
|
||||
showsVerticalScrollIndicator={false}
|
||||
/>
|
||||
</View>
|
||||
|
||||
</ModalFloat>
|
||||
)
|
||||
}
|
||||
116
components/project/modalListDetailTugasProject.tsx
Normal file
116
components/project/modalListDetailTugasProject.tsx
Normal file
@@ -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<Props[]>([])
|
||||
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 (
|
||||
<ModalFloat
|
||||
title="Detail Tanggal dan Waktu Tugas"
|
||||
isVisible={isVisible}
|
||||
setVisible={setVisible}
|
||||
buttonHide
|
||||
>
|
||||
<View style={[{ height: tinggiFix }]} >
|
||||
{
|
||||
loading ?
|
||||
arrSkeleton.map((item: any, i: number) => {
|
||||
return (
|
||||
<Skeleton key={i} width={100} widthType="percent" height={40} borderRadius={5} />
|
||||
)
|
||||
})
|
||||
:
|
||||
data.length > 0 ?
|
||||
(
|
||||
<VirtualizedList
|
||||
data={data}
|
||||
getItemCount={() => data.length}
|
||||
getItem={getItem}
|
||||
renderItem={({ item, index }: { item: Props, index: number }) => {
|
||||
return (
|
||||
<View key={index} style={[Styles.borderBottom, Styles.pv05]}>
|
||||
<Text style={[Styles.textDefaultSemiBold]}>{item.date}</Text>
|
||||
<View style={[Styles.rowSpaceBetween]}>
|
||||
<View style={[{ width: "48%" }]}>
|
||||
<InputDate
|
||||
mode="time"
|
||||
disable
|
||||
onChange={(val) => { }}
|
||||
value={item.timeStart}
|
||||
label="Waktu Awal"
|
||||
placeholder="--:--"
|
||||
/>
|
||||
</View>
|
||||
<View style={[{ width: "48%" }]}>
|
||||
<InputDate
|
||||
onChange={(val) => { }}
|
||||
mode="time"
|
||||
value={item.timeEnd}
|
||||
label="Waktu Akhir"
|
||||
placeholder="--:--"
|
||||
disable
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
)
|
||||
}}
|
||||
keyExtractor={(item, index) => String(index)}
|
||||
showsVerticalScrollIndicator={false}
|
||||
/>
|
||||
)
|
||||
:
|
||||
<Text style={[Styles.textDefault, Styles.cGray, { textAlign: 'center' }]} >Tidak ada data</Text>
|
||||
}
|
||||
</View>
|
||||
|
||||
</ModalFloat>
|
||||
)
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
import { ConstEnv } from "@/constants/ConstEnv";
|
||||
import Styles from "@/constants/Styles";
|
||||
import { apiDeleteFileProject, apiGetProjectOne } from "@/lib/api";
|
||||
import { setUpdateProject } from "@/lib/projectUpdate";
|
||||
@@ -90,17 +91,11 @@ 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)
|
||||
setLoadingOpen(true)
|
||||
let remoteUrl = 'https://wibu-storage.wibudev.com/api/files/' + selectFile?.idStorage;
|
||||
let remoteUrl = ConstEnv.url_storage + '/files/' + selectFile?.idStorage;
|
||||
const fileName = selectFile?.name + '.' + selectFile?.extension;
|
||||
let localPath = `${FileSystem.documentDirectory}/${fileName}`;
|
||||
const mimeType = mime.lookup(fileName)
|
||||
@@ -178,14 +173,6 @@ export default function SectionFile({ status, member, refreshing }: { status: nu
|
||||
openFile()
|
||||
}}
|
||||
/>
|
||||
{/* <MenuItemRow
|
||||
icon={<MaterialCommunityIcons name="download" color="black" size={25} />}
|
||||
title="Download"
|
||||
onPress={() => {
|
||||
// download()
|
||||
// setModal(false)
|
||||
}}
|
||||
/> */}
|
||||
{
|
||||
!member && (entityUser.role == "user" || entityUser.role == "coadmin") ? <></>
|
||||
:
|
||||
|
||||
140
components/project/sectionLink.tsx
Normal file
140
components/project/sectionLink.tsx
Normal file
@@ -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<Props[]>([]);
|
||||
const update = useSelector((state: any) => state.projectUpdate)
|
||||
const dispatch = useDispatch()
|
||||
const [selectLink, setSelectLink] = useState<Props | null>(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 &&
|
||||
<>
|
||||
<View style={[Styles.mb15]}>
|
||||
<Text style={[Styles.textDefaultSemiBold, Styles.mv05]}>Link</Text>
|
||||
<View style={[Styles.wrapPaper]}>
|
||||
{
|
||||
data.map((item, index) => {
|
||||
return (
|
||||
<BorderBottomItem
|
||||
key={index}
|
||||
borderType="all"
|
||||
icon={<Feather name="link" size={25} color="black" />}
|
||||
title={item.link}
|
||||
titleWeight="normal"
|
||||
onPress={() => { setSelectLink(item); setModal(true) }}
|
||||
width={65}
|
||||
/>
|
||||
)
|
||||
})
|
||||
}
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<DrawerBottom animation="slide" isVisible={isModal} setVisible={setModal} title="Menu">
|
||||
<View style={Styles.rowItemsCenter}>
|
||||
<MenuItemRow
|
||||
icon={<Feather name="external-link" color="black" size={25} />}
|
||||
title="Buka Link"
|
||||
onPress={() => {
|
||||
Linking.openURL(urlCompleted(String(selectLink?.link)))
|
||||
}}
|
||||
/>
|
||||
{
|
||||
!member && (entityUser.role == "user" || entityUser.role == "coadmin") ? <></>
|
||||
:
|
||||
<MenuItemRow
|
||||
icon={<Ionicons name="trash" color="black" size={25} />}
|
||||
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()
|
||||
}
|
||||
})
|
||||
|
||||
}}
|
||||
/>
|
||||
}
|
||||
</View>
|
||||
</DrawerBottom>
|
||||
</>
|
||||
}
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
import { ConstEnv } from "@/constants/ConstEnv";
|
||||
import Styles from "@/constants/Styles";
|
||||
import { apiDeleteProjectMember, apiGetProjectOne } from "@/lib/api";
|
||||
import { setUpdateProject } from "@/lib/projectUpdate";
|
||||
@@ -112,7 +113,7 @@ export default function SectionMember({ status, refreshing }: { status: number |
|
||||
<BorderBottomItem
|
||||
key={index}
|
||||
borderType="bottom"
|
||||
icon={<ImageUser src={`https://wibu-storage.wibudev.com/api/files/${item.img}`} />}
|
||||
icon={<ImageUser src={`${ConstEnv.url_storage}/files/${item.img}`} />}
|
||||
title={item.name}
|
||||
onPress={() => {
|
||||
if (status == 3) return
|
||||
|
||||
55
components/project/sectionReportProject.tsx
Normal file
55
components/project/sectionReportProject.tsx
Normal file
@@ -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 &&
|
||||
<View style={[Styles.mb15, Styles.mt10]}>
|
||||
<Text style={[Styles.textDefaultSemiBold, Styles.mv05]}>
|
||||
Laporan Kegiatan
|
||||
</Text>
|
||||
<View style={[Styles.wrapPaper]}>
|
||||
<TextExpandable content={data} maxLines={2} />
|
||||
</View>
|
||||
</View>
|
||||
}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -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<Props[]>([]);
|
||||
const [loading, setLoading] = useState(true)
|
||||
@@ -188,6 +190,24 @@ export default function SectionTanggalTugasProject({ status, member, refreshing
|
||||
}}
|
||||
/>
|
||||
|
||||
<MenuItemRow
|
||||
icon={
|
||||
<MaterialCommunityIcons
|
||||
name="clock-time-three-outline"
|
||||
color="black"
|
||||
size={25}
|
||||
/>
|
||||
}
|
||||
title="Detail Waktu"
|
||||
onPress={() => {
|
||||
setModal(false);
|
||||
setTimeout(() => {
|
||||
setModalDetail(true)
|
||||
}, 600)
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
<View style={[Styles.rowItemsCenter, Styles.mt15]}>
|
||||
<MenuItemRow
|
||||
icon={<Ionicons name="trash" color="black" size={25} />}
|
||||
title="Hapus Tugas"
|
||||
@@ -213,6 +233,12 @@ export default function SectionTanggalTugasProject({ status, member, refreshing
|
||||
open={isSelect}
|
||||
valChoose={String(tugas.status)}
|
||||
/>
|
||||
|
||||
<ModalListDetailTugasProject
|
||||
isVisible={modalDetail}
|
||||
setVisible={setModalDetail}
|
||||
idTask={tugas.id}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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 (
|
||||
<>
|
||||
{
|
||||
@@ -80,7 +101,7 @@ export default function HeaderRightTaskDetail({ id, division, status }: Props) {
|
||||
:
|
||||
<ButtonMenuHeader onPress={() => { setVisible(true) }} />
|
||||
}
|
||||
<DrawerBottom animation="slide" isVisible={isVisible} setVisible={setVisible} title="Menu">
|
||||
<DrawerBottom animation="slide" isVisible={isVisible} setVisible={setVisible} title="Menu" height={30}>
|
||||
<View style={Styles.rowItemsCenter}>
|
||||
<MenuItemRow
|
||||
icon={<AntDesign name="pluscircle" color="black" size={25} />}
|
||||
@@ -102,38 +123,63 @@ export default function HeaderRightTaskDetail({ id, division, status }: Props) {
|
||||
}}
|
||||
disabled={status == 3}
|
||||
/>
|
||||
|
||||
<MenuItemRow
|
||||
icon={<Feather name="link" color="black" size={25} />}
|
||||
title="Tambah Link"
|
||||
onPress={() => {
|
||||
if (status == 3) return
|
||||
setVisible(false)
|
||||
setTimeout(() => {
|
||||
setAddLink(true)
|
||||
}, 600)
|
||||
}}
|
||||
disabled={status == 3}
|
||||
/>
|
||||
</View>
|
||||
<View style={[Styles.rowItemsCenter, Styles.mt15]}>
|
||||
<MenuItemRow
|
||||
icon={<MaterialCommunityIcons name="file-document" color="black" size={25} />}
|
||||
title="Laporan"
|
||||
onPress={() => {
|
||||
if (status == 3) return
|
||||
setVisible(false)
|
||||
router.push(`./${id}/report`)
|
||||
}}
|
||||
disabled={status == 3}
|
||||
/>
|
||||
|
||||
{
|
||||
((entityUser.role != "user" && entityUser.role != "coadmin") || isAdminDivision)
|
||||
&&
|
||||
<MenuItemRow
|
||||
icon={<MaterialIcons name="groups" color="black" size={25} />}
|
||||
title="Tambah Anggota"
|
||||
onPress={() => {
|
||||
if (status == 3) return
|
||||
setVisible(false)
|
||||
router.push(`./${id}/add-member`)
|
||||
}}
|
||||
disabled={status == 3}
|
||||
/>
|
||||
|
||||
<>
|
||||
<MenuItemRow
|
||||
icon={<MaterialIcons name="groups" color="black" size={25} />}
|
||||
title="Tambah Anggota"
|
||||
onPress={() => {
|
||||
if (status == 3) return
|
||||
setVisible(false)
|
||||
router.push(`./${id}/add-member`)
|
||||
}}
|
||||
disabled={status == 3}
|
||||
/>
|
||||
<MenuItemRow
|
||||
icon={<MaterialCommunityIcons name="pencil-outline" color="black" size={25} />}
|
||||
title="Edit"
|
||||
onPress={() => {
|
||||
if (status == 3) return
|
||||
setVisible(false)
|
||||
router.push(`./${id}/edit`)
|
||||
}}
|
||||
disabled={status == 3}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
|
||||
</View>
|
||||
|
||||
{
|
||||
((entityUser.role != "user" && entityUser.role != "coadmin") || isAdminDivision)
|
||||
&&
|
||||
<View style={[Styles.rowItemsCenter, Styles.mt15]}>
|
||||
<MenuItemRow
|
||||
icon={<MaterialCommunityIcons name="pencil-outline" color="black" size={25} />}
|
||||
title="Edit"
|
||||
onPress={() => {
|
||||
if (status == 3) return
|
||||
setVisible(false)
|
||||
router.push(`./${id}/edit`)
|
||||
}}
|
||||
disabled={status == 3}
|
||||
/>
|
||||
{
|
||||
status == 3
|
||||
?
|
||||
@@ -162,7 +208,24 @@ export default function HeaderRightTaskDetail({ id, division, status }: Props) {
|
||||
}
|
||||
</View>
|
||||
}
|
||||
</DrawerBottom>
|
||||
</DrawerBottom >
|
||||
|
||||
<ModalFloat
|
||||
title="Tambah Link"
|
||||
isVisible={isAddLink}
|
||||
setVisible={() => { setAddLink(false) }}
|
||||
onSubmit={() => { handleAddLink() }}
|
||||
disableSubmit={link == ""}
|
||||
>
|
||||
<View>
|
||||
<InputForm
|
||||
type="default"
|
||||
placeholder="Masukkan link"
|
||||
value={link}
|
||||
onChange={(text) => { setLink(text) }}
|
||||
/>
|
||||
</View>
|
||||
</ModalFloat>
|
||||
</>
|
||||
)
|
||||
}
|
||||
135
components/task/modalAddDetailTugasTask.tsx
Normal file
135
components/task/modalAddDetailTugasTask.tsx
Normal file
@@ -0,0 +1,135 @@
|
||||
import Styles from "@/constants/Styles";
|
||||
import { stringToDateTime } from "@/lib/fun_stringToDate";
|
||||
import { useEffect, useState } from "react";
|
||||
import { Dimensions, View, VirtualizedList } from "react-native";
|
||||
import { InputDate } from "../inputDate";
|
||||
import ModalFloat from "../modalFloat";
|
||||
import Text from "../Text";
|
||||
|
||||
interface Props {
|
||||
date: string;
|
||||
timeStart: string;
|
||||
timeEnd: string;
|
||||
}
|
||||
|
||||
export default function ModalAddDetailTugasTask({ isVisible, setVisible, dataTanggal, onSubmit }: { isVisible: boolean, setVisible: (value: boolean) => void, dataTanggal: Props[], onSubmit: (data: Props[]) => void }) {
|
||||
const [data, setData] = useState<Props[]>(dataTanggal)
|
||||
const tinggiScreen = Dimensions.get("window").height;
|
||||
const tinggiFix = tinggiScreen * 70 / 100;
|
||||
const [error, setError] = useState<any>([])
|
||||
|
||||
useEffect(() => {
|
||||
if (isVisible) {
|
||||
setData(dataTanggal)
|
||||
setError([])
|
||||
}
|
||||
}, [isVisible, dataTanggal])
|
||||
|
||||
|
||||
const getItem = (_data: unknown, index: number): Props => ({
|
||||
date: data[index].date,
|
||||
timeStart: data[index].timeStart,
|
||||
timeEnd: data[index].timeEnd,
|
||||
})
|
||||
|
||||
function settingError(date: string, cat: 'timeStart' | 'timeEnd', val: boolean) {
|
||||
const ada = error.find((item: any) => item.date == date)
|
||||
if (ada) {
|
||||
setError(error.map((item: any) => {
|
||||
if (item.date == date) {
|
||||
return { ...item, [cat]: val }
|
||||
}
|
||||
return item
|
||||
}))
|
||||
} else {
|
||||
setError([...error, { date, [cat]: val }])
|
||||
}
|
||||
}
|
||||
|
||||
function validationForm(cat: "timeStart" | "timeEnd", val: string, date: string) {
|
||||
if (cat == "timeEnd") {
|
||||
const start = stringToDateTime("", String(data.find((item) => item.date == date)?.timeStart))
|
||||
const end = stringToDateTime("", val)
|
||||
const timestampAwal = start.getTime()
|
||||
const timestampAkhir = end.getTime()
|
||||
if (val == "" || val == null || timestampAwal > timestampAkhir) {
|
||||
settingError(date, "timeEnd", true)
|
||||
} else {
|
||||
settingError(date, "timeEnd", false)
|
||||
}
|
||||
} else {
|
||||
const end = stringToDateTime("", String(data.find((item) => item.date == date)?.timeEnd))
|
||||
const start = stringToDateTime("", val)
|
||||
const timestampAwal = start.getTime()
|
||||
const timestampAkhir = end.getTime()
|
||||
|
||||
if (val == "" || val == null || timestampAwal > timestampAkhir) {
|
||||
settingError(date, "timeEnd", true)
|
||||
} else {
|
||||
settingError(date, "timeEnd", false)
|
||||
}
|
||||
}
|
||||
|
||||
setData(data.map((item) => {
|
||||
if (item.date == date) {
|
||||
return { ...item, [cat]: val }
|
||||
}
|
||||
return item
|
||||
}))
|
||||
}
|
||||
|
||||
return (
|
||||
<ModalFloat
|
||||
title="Detail Tanggal dan Waktu Tugas"
|
||||
isVisible={isVisible}
|
||||
setVisible={setVisible}
|
||||
disableSubmit={Object.values(error).some((val: any) => val.timeEnd == true || val.timeStart == true)}
|
||||
onSubmit={() => {
|
||||
onSubmit(data)
|
||||
setVisible(false)
|
||||
}}
|
||||
>
|
||||
<View style={[{ height: tinggiFix }]} >
|
||||
<VirtualizedList
|
||||
data={data}
|
||||
getItemCount={() => data.length}
|
||||
getItem={getItem}
|
||||
renderItem={({ item, index }: { item: Props, index: number }) => {
|
||||
return (
|
||||
<View key={index} style={[Styles.borderBottom, Styles.pv05]}>
|
||||
<Text style={[Styles.textDefaultSemiBold]}>{item.date}</Text>
|
||||
<View style={[Styles.rowSpaceBetween]}>
|
||||
<View style={[{ width: "48%" }]}>
|
||||
<InputDate
|
||||
mode="time"
|
||||
onChange={(val) => { validationForm("timeStart", val, item.date) }}
|
||||
value={item.timeStart}
|
||||
label="Waktu Awal"
|
||||
placeholder="--:--"
|
||||
error={error.find((error: any) => error.date == item.date)?.timeStart}
|
||||
errorText="Waktu awal tidak valid"
|
||||
/>
|
||||
</View>
|
||||
<View style={[{ width: "48%" }]}>
|
||||
<InputDate
|
||||
onChange={(val) => { validationForm("timeEnd", val, item.date) }}
|
||||
mode="time"
|
||||
value={item.timeEnd}
|
||||
label="Waktu Akhir"
|
||||
placeholder="--:--"
|
||||
error={error.find((error: any) => error.date == item.date)?.timeEnd}
|
||||
errorText="Waktu akhir harus lebih dari waktu awal"
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
)
|
||||
}}
|
||||
keyExtractor={(item, index) => String(index)}
|
||||
showsVerticalScrollIndicator={false}
|
||||
/>
|
||||
</View>
|
||||
|
||||
</ModalFloat>
|
||||
)
|
||||
}
|
||||
116
components/task/modalListDetailTugasTask.tsx
Normal file
116
components/task/modalListDetailTugasTask.tsx
Normal file
@@ -0,0 +1,116 @@
|
||||
import Styles from "@/constants/Styles";
|
||||
import { apiGetProjectTask, apiGetTaskTugas } 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 ModalListDetailTugasTask({ isVisible, setVisible, idTask }: { isVisible: boolean, setVisible: (value: boolean) => void, idTask: string }) {
|
||||
const [data, setData] = useState<Props[]>([])
|
||||
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 apiGetTaskTugas({ 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 (
|
||||
<ModalFloat
|
||||
title="Detail Tanggal dan Waktu Tugas"
|
||||
isVisible={isVisible}
|
||||
setVisible={setVisible}
|
||||
buttonHide
|
||||
>
|
||||
<View style={[{ height: tinggiFix }]} >
|
||||
{
|
||||
loading ?
|
||||
arrSkeleton.map((item: any, i: number) => {
|
||||
return (
|
||||
<Skeleton key={i} width={100} widthType="percent" height={40} borderRadius={5} />
|
||||
)
|
||||
})
|
||||
:
|
||||
data.length > 0 ?
|
||||
(
|
||||
<VirtualizedList
|
||||
data={data}
|
||||
getItemCount={() => data.length}
|
||||
getItem={getItem}
|
||||
renderItem={({ item, index }: { item: Props, index: number }) => {
|
||||
return (
|
||||
<View key={index} style={[Styles.borderBottom, Styles.pv05]}>
|
||||
<Text style={[Styles.textDefaultSemiBold]}>{item.date}</Text>
|
||||
<View style={[Styles.rowSpaceBetween]}>
|
||||
<View style={[{ width: "48%" }]}>
|
||||
<InputDate
|
||||
mode="time"
|
||||
disable
|
||||
onChange={(val) => { }}
|
||||
value={item.timeStart}
|
||||
label="Waktu Awal"
|
||||
placeholder="--:--"
|
||||
/>
|
||||
</View>
|
||||
<View style={[{ width: "48%" }]}>
|
||||
<InputDate
|
||||
onChange={(val) => { }}
|
||||
mode="time"
|
||||
value={item.timeEnd}
|
||||
label="Waktu Akhir"
|
||||
placeholder="--:--"
|
||||
disable
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
)
|
||||
}}
|
||||
keyExtractor={(item, index) => String(index)}
|
||||
showsVerticalScrollIndicator={false}
|
||||
/>
|
||||
)
|
||||
:
|
||||
<Text style={[Styles.textDefault, Styles.cGray, { textAlign: 'center' }]} >Tidak ada data</Text>
|
||||
}
|
||||
</View>
|
||||
|
||||
</ModalFloat>
|
||||
)
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
import { ConstEnv } from "@/constants/ConstEnv";
|
||||
import Styles from "@/constants/Styles";
|
||||
import { apiDeleteFileTask, apiGetTaskOne } from "@/lib/api";
|
||||
import { setUpdateTask } from "@/lib/taskUpdate";
|
||||
@@ -27,7 +28,7 @@ type Props = {
|
||||
idStorage: string
|
||||
}
|
||||
|
||||
export default function SectionFileTask({refreshing}: {refreshing: boolean}) {
|
||||
export default function SectionFileTask({ refreshing }: { refreshing: boolean }) {
|
||||
const [isModal, setModal] = useState(false)
|
||||
const { token, decryptToken } = useAuthSession()
|
||||
const { detail } = useLocalSearchParams<{ detail: string }>()
|
||||
@@ -68,7 +69,7 @@ export default function SectionFileTask({refreshing}: {refreshing: boolean}) {
|
||||
const openFile = () => {
|
||||
setModal(false)
|
||||
setLoadingOpen(true)
|
||||
let remoteUrl = 'https://wibu-storage.wibudev.com/api/files/' + selectFile?.idStorage;
|
||||
let remoteUrl = ConstEnv.url_storage + '/files/' + selectFile?.idStorage;
|
||||
const fileName = selectFile?.name + '.' + selectFile?.extension;
|
||||
let localPath = `${FileSystem.documentDirectory}/${fileName}`;
|
||||
const mimeType = mime.lookup(fileName)
|
||||
@@ -160,17 +161,8 @@ export default function SectionFileTask({refreshing}: {refreshing: boolean}) {
|
||||
title="Lihat / Share"
|
||||
onPress={() => {
|
||||
openFile()
|
||||
// setModal(false)
|
||||
}}
|
||||
/>
|
||||
{/* <MenuItemRow
|
||||
icon={<MaterialCommunityIcons name="download" color="black" size={25} />}
|
||||
title="Download"
|
||||
onPress={() => {
|
||||
setModal(false)
|
||||
}}
|
||||
/> */}
|
||||
|
||||
<MenuItemRow
|
||||
icon={<Ionicons name="trash" color="black" size={25} />}
|
||||
title="Hapus"
|
||||
|
||||
122
components/task/sectionLinkTask.tsx
Normal file
122
components/task/sectionLinkTask.tsx
Normal file
@@ -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<Props[]>([])
|
||||
const update = useSelector((state: any) => state.taskUpdate)
|
||||
const dispatch = useDispatch()
|
||||
const [selectLink, setSelectLink] = useState<Props | null>(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 &&
|
||||
<>
|
||||
<View style={[Styles.mb15]}>
|
||||
<Text style={[Styles.textDefaultSemiBold, Styles.mv05]}>Link</Text>
|
||||
<View style={[Styles.wrapPaper]}>
|
||||
{
|
||||
data.map((item, index) => {
|
||||
return (
|
||||
<BorderBottomItem
|
||||
key={index}
|
||||
borderType="all"
|
||||
icon={<Feather name="link" size={25} color="black" />}
|
||||
title={item.link}
|
||||
titleWeight="normal"
|
||||
onPress={() => { setSelectLink(item); setModal(true) }}
|
||||
width={65}
|
||||
/>
|
||||
)
|
||||
})
|
||||
}
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<DrawerBottom animation="slide" isVisible={isModal} setVisible={setModal} title="Menu">
|
||||
<View style={Styles.rowItemsCenter}>
|
||||
<MenuItemRow
|
||||
icon={<Feather name="external-link" color="black" size={25} />}
|
||||
title="Buka Link"
|
||||
onPress={() => {
|
||||
Linking.openURL(urlCompleted(String(selectLink?.link)))
|
||||
}}
|
||||
/>
|
||||
<MenuItemRow
|
||||
icon={<Ionicons name="trash" color="black" size={25} />}
|
||||
title="Hapus"
|
||||
onPress={() => {
|
||||
setModal(false)
|
||||
AlertKonfirmasi({
|
||||
title: 'Konfirmasi',
|
||||
desc: 'Apakah Anda yakin ingin menghapus link ini? Link yang dihapus tidak dapat dikembalikan',
|
||||
onPress: () => { handleDelete() }
|
||||
})
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
</DrawerBottom>
|
||||
</>
|
||||
}
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
import { ConstEnv } from "@/constants/ConstEnv";
|
||||
import Styles from "@/constants/Styles";
|
||||
import { apiDeleteTaskMember, apiGetTaskOne } from "@/lib/api";
|
||||
import { setUpdateTask } from "@/lib/taskUpdate";
|
||||
@@ -114,9 +115,7 @@ export default function SectionMemberTask({ refreshing }: { refreshing: boolean
|
||||
key={index}
|
||||
borderType="bottom"
|
||||
icon={
|
||||
<ImageUser
|
||||
src={`https://wibu-storage.wibudev.com/api/files/${item.img}`}
|
||||
/>
|
||||
<ImageUser src={`${ConstEnv.url_storage}/files/${item.img}`} />
|
||||
}
|
||||
title={item.name}
|
||||
onPress={() => {
|
||||
|
||||
53
components/task/sectionReportTask.tsx
Normal file
53
components/task/sectionReportTask.tsx
Normal file
@@ -0,0 +1,53 @@
|
||||
import Styles from "@/constants/Styles";
|
||||
import { apiGetTaskOne } 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 SectionReportTask({ refreshing }: { refreshing: boolean }) {
|
||||
const update = useSelector((state: any) => 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 &&
|
||||
<View style={[Styles.mb15, Styles.mt10]}>
|
||||
<Text style={[Styles.textDefaultSemiBold, Styles.mv05]}>
|
||||
Laporan Kegiatan
|
||||
</Text>
|
||||
<View style={[Styles.wrapPaper]}>
|
||||
<TextExpandable content={data} maxLines={2} />
|
||||
</View>
|
||||
</View>
|
||||
}
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -15,6 +15,7 @@ import MenuItemRow from "../menuItemRow";
|
||||
import ModalSelect from "../modalSelect";
|
||||
import SkeletonTask from "../skeletonTask";
|
||||
import Text from "../Text";
|
||||
import ModalListDetailTugasTask from "./modalListDetailTugasTask";
|
||||
|
||||
|
||||
type Props = {
|
||||
@@ -25,7 +26,7 @@ type Props = {
|
||||
dateEnd: string;
|
||||
}
|
||||
|
||||
export default function SectionTanggalTugasTask({refreshing}: {refreshing: boolean}) {
|
||||
export default function SectionTanggalTugasTask({ refreshing }: { refreshing: boolean }) {
|
||||
const dispatch = useDispatch()
|
||||
const update = useSelector((state: any) => state.taskUpdate)
|
||||
const [isModal, setModal] = useState(false)
|
||||
@@ -33,6 +34,7 @@ export default function SectionTanggalTugasTask({refreshing}: {refreshing: boole
|
||||
const { token, decryptToken } = useAuthSession()
|
||||
const [loading, setLoading] = useState(true)
|
||||
const arrSkeleton = Array.from({ length: 5 })
|
||||
const [modalDetail, setModalDetail] = useState(false)
|
||||
const { id, detail } = useLocalSearchParams<{ id: string, detail: string }>();
|
||||
const [data, setData] = useState<Props[]>([])
|
||||
const [tugas, setTugas] = useState({
|
||||
@@ -171,7 +173,24 @@ export default function SectionTanggalTugasTask({refreshing}: {refreshing: boole
|
||||
router.push(`./update/${tugas.id}`)
|
||||
}}
|
||||
/>
|
||||
|
||||
<MenuItemRow
|
||||
icon={
|
||||
<MaterialCommunityIcons
|
||||
name="clock-time-three-outline"
|
||||
color="black"
|
||||
size={25}
|
||||
/>
|
||||
}
|
||||
title="Detail Waktu"
|
||||
onPress={() => {
|
||||
setModal(false);
|
||||
setTimeout(() => {
|
||||
setModalDetail(true)
|
||||
}, 600)
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
<View style={[Styles.rowItemsCenter, Styles.mt15]}>
|
||||
<MenuItemRow
|
||||
icon={<Ionicons name="trash" color="black" size={25} />}
|
||||
title="Hapus Tugas"
|
||||
@@ -198,6 +217,12 @@ export default function SectionTanggalTugasTask({refreshing}: {refreshing: boole
|
||||
open={isSelect}
|
||||
valChoose={String(tugas.status)}
|
||||
/>
|
||||
|
||||
<ModalListDetailTugasTask
|
||||
isVisible={modalDetail}
|
||||
setVisible={setModalDetail}
|
||||
idTask={tugas.id}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
83
components/textExpandable.tsx
Normal file
83
components/textExpandable.tsx
Normal file
@@ -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 (
|
||||
<View>
|
||||
{/* Hidden full text for measurement */}
|
||||
<View style={Styles.hidden}>
|
||||
<Text style={Styles.textDefault} onLayout={measureFull}>
|
||||
{content}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
{/* Collapsed text for measurement */}
|
||||
<View style={Styles.hidden}>
|
||||
<Text
|
||||
numberOfLines={maxLines}
|
||||
style={Styles.textDefault}
|
||||
onLayout={measureCollapsed}
|
||||
ellipsizeMode="tail"
|
||||
>
|
||||
{content}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
{/* Animated visible text */}
|
||||
<Animated.View style={{ height: animatedHeight, overflow: 'hidden' }}>
|
||||
<Text
|
||||
style={Styles.textDefault}
|
||||
numberOfLines={isExpanded ? undefined : maxLines}
|
||||
ellipsizeMode="tail"
|
||||
>
|
||||
{content}
|
||||
</Text>
|
||||
</Animated.View>
|
||||
|
||||
{shouldShowMore && (
|
||||
<Pressable onPress={toggleExpand}>
|
||||
<Text style={Styles.textLink}>
|
||||
{isExpanded ? 'View Less' : 'View More'}
|
||||
</Text>
|
||||
</Pressable>
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
@@ -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';
|
||||
|
||||
|
||||
6
constants/ConstEnv.ts
Normal file
6
constants/ConstEnv.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import Constants from 'expo-constants';
|
||||
|
||||
export const ConstEnv = {
|
||||
url_storage: Constants?.expoConfig?.extra?.URL_STORAGE,
|
||||
pass_encrypt: Constants?.expoConfig?.extra?.PASS_ENC
|
||||
}
|
||||
@@ -45,8 +45,7 @@ const Styles = StyleSheet.create({
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
textLink: {
|
||||
lineHeight: 30,
|
||||
fontSize: 16,
|
||||
fontSize: 14,
|
||||
color: '#0a7ea4',
|
||||
},
|
||||
textInformation: {
|
||||
@@ -270,6 +269,15 @@ const Styles = StyleSheet.create({
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'center'
|
||||
},
|
||||
btnLainnya: {
|
||||
alignSelf: 'flex-start',
|
||||
backgroundColor: '#19345E',
|
||||
paddingVertical: 5,
|
||||
marginVertical: 5
|
||||
},
|
||||
btnDisabled: {
|
||||
backgroundColor: '#d6d8f6',
|
||||
},
|
||||
btnMenuRow: {
|
||||
width: '33%',
|
||||
alignItems: 'center'
|
||||
@@ -585,6 +593,19 @@ const Styles = StyleSheet.create({
|
||||
width: '90%',
|
||||
borderWidth: 1,
|
||||
borderColor: '#d6d8f6',
|
||||
},
|
||||
absoluteIconPicker: {
|
||||
backgroundColor: '#384288',
|
||||
padding: 5,
|
||||
borderRadius: 100,
|
||||
bottom: 5,
|
||||
right: 5,
|
||||
position: 'absolute'
|
||||
},
|
||||
hidden: {
|
||||
position: 'absolute',
|
||||
opacity: 0,
|
||||
zIndex: -1,
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
19
index.js
Normal file
19
index.js
Normal file
@@ -0,0 +1,19 @@
|
||||
// index.js
|
||||
import 'expo-router/entry'; // ⬅️ wajib ada agar expo-router tetap bekerja
|
||||
|
||||
import { getApp } from '@react-native-firebase/app';
|
||||
import { getMessaging, setBackgroundMessageHandler } from '@react-native-firebase/messaging';
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
|
||||
// ✅ Ambil instance messaging modular
|
||||
const mess = getMessaging(getApp());
|
||||
|
||||
// ✅ Firebase background handler — wajib diletakkan DI SINI
|
||||
setBackgroundMessageHandler(mess, async remoteMessage => {
|
||||
const screen = remoteMessage?.data?.category;
|
||||
const content = remoteMessage?.data?.content;
|
||||
|
||||
if (screen && content) {
|
||||
await AsyncStorage.setItem('navigateOnOpen', JSON.stringify({ screen, content }));
|
||||
}
|
||||
});
|
||||
@@ -409,6 +409,7 @@
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = mobiledarmasaba/mobiledarmasaba.entitlements;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = 5W96P6CVXB;
|
||||
INFOPLIST_FILE = mobiledarmasaba/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.1;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
|
||||
60
lib/api.ts
60
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.89:3000/api',
|
||||
// baseURL: 'https://stg-darmasaba.wibudev.com/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
|
||||
}
|
||||
@@ -150,7 +152,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 +266,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;
|
||||
};
|
||||
@@ -274,7 +276,12 @@ export const apiEditProject = async (data: { name: string, user: string }, id: s
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const apiCreateProjectTask = async ({ data, id }: { data: { name: string, dateStart: string, user: string, dateEnd: string }, id: string }) => {
|
||||
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, dataDetail: any[] }, id: string }) => {
|
||||
const response = await api.post(`/mobile/project/${id}`, data)
|
||||
return response.data;
|
||||
};
|
||||
@@ -294,12 +301,12 @@ 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;
|
||||
};
|
||||
|
||||
export const apiEditProjectTask = async ({ data, id }: { data: { title: string, dateStart: string, user: string, dateEnd: string }, id: string }) => {
|
||||
export const apiEditProjectTask = async ({ data, id }: { data: { title: string, dateStart: string, user: string, dateEnd: string, dataDetail: any[] }, id: string }) => {
|
||||
const response = await api.post(`/mobile/project/detail/${id}`, data)
|
||||
return response.data;
|
||||
};
|
||||
@@ -329,6 +336,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 +368,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 +519,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;
|
||||
};
|
||||
@@ -512,12 +529,12 @@ export const apiUpdateStatusTaskDivision = async (data: { user: string, status:
|
||||
return response.data
|
||||
};
|
||||
|
||||
export const apiGetTaskTugas = async ({ user, id }: { user: string, id: string }) => {
|
||||
const response = await api.get(`mobile/task/detail/${id}?user=${user}`);
|
||||
export const apiGetTaskTugas = async ({ user, id, cat }: { user: string, id: string, cat?: string }) => {
|
||||
const response = await api.get(`mobile/task/detail/${id}?user=${user}${cat ? `&cat=${cat}` : ""}`);
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const apiEditTaskTugas = async ({ data, id }: { data: { title: string, dateStart: string, user: string, dateEnd: string }, id: string }) => {
|
||||
export const apiEditTaskTugas = async ({ data, id }: { data: { title: string, dateStart: string, user: string, dateEnd: string, dataDetail: any[] }, id: string }) => {
|
||||
const response = await api.post(`/mobile/task/detail/${id}`, data)
|
||||
return response.data;
|
||||
};
|
||||
@@ -532,12 +549,17 @@ 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
|
||||
};
|
||||
|
||||
export const apiCreateTaskTugas = async ({ data, id }: { data: { title: string, dateStart: string, user: string, dateEnd: string, idDivision: string }, id: string }) => {
|
||||
export const apiCreateTaskTugas = async ({ data, id }: { data: { title: string, dateStart: string, user: string, dateEnd: string, idDivision: string, dataDetail: any[] }, id: string }) => {
|
||||
const response = await api.post(`/mobile/task/${id}`, data)
|
||||
return response.data;
|
||||
};
|
||||
@@ -569,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
|
||||
@@ -593,6 +620,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;
|
||||
|
||||
10
lib/firebaseDatabase.ts
Normal file
10
lib/firebaseDatabase.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { getApp } from '@react-native-firebase/app';
|
||||
import { getDatabase } from '@react-native-firebase/database';
|
||||
import Constants from 'expo-constants';
|
||||
|
||||
// Ganti URL sesuai punya kamu
|
||||
const DATABASE_URL = Constants?.expoConfig?.extra?.URL_FIREBASE_DB
|
||||
|
||||
export function getDB() {
|
||||
return getDatabase(getApp(), DATABASE_URL);
|
||||
}
|
||||
18
lib/fun_formatDateOnly.ts
Normal file
18
lib/fun_formatDateOnly.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import dayjs from "dayjs";
|
||||
import utc from "dayjs/plugin/utc";
|
||||
import timezone from "dayjs/plugin/timezone";
|
||||
import { DateType } from "react-native-ui-datepicker";
|
||||
|
||||
dayjs.extend(utc);
|
||||
dayjs.extend(timezone);
|
||||
|
||||
export function formatDateOnly(
|
||||
date?: DateType,
|
||||
format: "DD-MM-YYYY" | "YYYY-MM-DD" = "DD-MM-YYYY"
|
||||
) {
|
||||
if (!date) return "";
|
||||
|
||||
return dayjs(date)
|
||||
.tz(dayjs.tz.guess())
|
||||
.format(format);
|
||||
}
|
||||
14
lib/fun_getDatesInRange.ts
Normal file
14
lib/fun_getDatesInRange.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import dayjs from "dayjs";
|
||||
|
||||
export function getDatesInRange(startDate: string, endDate: string) {
|
||||
const dates = [];
|
||||
const currentDate = new Date(startDate);
|
||||
const endDateNew = new Date(endDate);
|
||||
|
||||
while (currentDate <= endDateNew) {
|
||||
dates.push(dayjs(new Date(currentDate)).format("DD-MM-YYYY"));
|
||||
currentDate.setDate(currentDate.getDate() + 1);
|
||||
}
|
||||
|
||||
return dates;
|
||||
}
|
||||
4
lib/fun_urlCompleted.ts
Normal file
4
lib/fun_urlCompleted.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export function urlCompleted(url: string) {
|
||||
if (url.startsWith('http://') || url.startsWith('https://')) return url
|
||||
return 'https://' + url
|
||||
}
|
||||
14
lib/groupChoose.ts
Normal file
14
lib/groupChoose.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { createSlice } from '@reduxjs/toolkit';
|
||||
|
||||
const groupChoose = createSlice({
|
||||
name: 'groupChoose',
|
||||
initialState: '',
|
||||
reducers: {
|
||||
setGroupChoose: (state, action) => {
|
||||
return action.payload;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const { setGroupChoose } = groupChoose.actions;
|
||||
export default groupChoose.reducer;
|
||||
@@ -7,7 +7,9 @@ const projectUpdate = createSlice({
|
||||
progress: false,
|
||||
task: false,
|
||||
file: false,
|
||||
member: false
|
||||
member: false,
|
||||
link: false,
|
||||
report: false,
|
||||
},
|
||||
reducers: {
|
||||
setUpdateProject: (state, action) => {
|
||||
|
||||
@@ -19,6 +19,7 @@ import projectUpdate from './projectUpdate';
|
||||
import taskCreate from './taskCreate';
|
||||
import taskUpdate from './taskUpdate';
|
||||
import userReducer from './userSlice';
|
||||
import groupChoose from './groupChoose';
|
||||
|
||||
const store = configureStore({
|
||||
reducer: {
|
||||
@@ -42,6 +43,7 @@ const store = configureStore({
|
||||
dokumenUpdate: dokumenUpdate,
|
||||
notificationUpdate: notificationUpdate,
|
||||
calendarCreate: calendarCreate,
|
||||
groupChoose: groupChoose,
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -7,7 +7,9 @@ const taskUpdate = createSlice({
|
||||
progress: false,
|
||||
task: false,
|
||||
file: false,
|
||||
member: false
|
||||
member: false,
|
||||
link: false,
|
||||
report: false,
|
||||
},
|
||||
reducers: {
|
||||
setUpdateTask: (state, action) => {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
import { getApp, getApps, initializeApp } from '@react-native-firebase/app';
|
||||
import messaging, { getMessaging } from '@react-native-firebase/messaging';
|
||||
import { getMessaging, registerDeviceForRemoteMessages, setAutoInitEnabled } from '@react-native-firebase/messaging';
|
||||
import * as Notifications from 'expo-notifications';
|
||||
import { useEffect } from 'react';
|
||||
import { PermissionsAndroid, Platform } from 'react-native';
|
||||
|
||||
@@ -20,25 +20,8 @@ const initializeFirebase = async () => {
|
||||
try {
|
||||
const app = getApps().length ? getApp() : initializeApp(RNfirebaseConfig);
|
||||
const mess = getMessaging(app);
|
||||
|
||||
await messaging().registerDeviceForRemoteMessages();
|
||||
|
||||
// Set auto initialization and background message handler
|
||||
mess.setAutoInitEnabled(true);
|
||||
// mess.setBackgroundMessageHandler(async remoteMessage => {
|
||||
// // console.log('Message handled in the background!', remoteMessage);
|
||||
// // pushToPage(String(remoteMessage?.data?.category), String(remoteMessage?.data?.content))
|
||||
// });
|
||||
|
||||
mess.setBackgroundMessageHandler(async remoteMessage => {
|
||||
const screen = remoteMessage?.data?.category;
|
||||
const content = remoteMessage?.data?.content;
|
||||
|
||||
if (screen && content) {
|
||||
await AsyncStorage.setItem('navigateOnOpen', JSON.stringify({ screen, content }));
|
||||
}
|
||||
});
|
||||
|
||||
await registerDeviceForRemoteMessages(mess);
|
||||
setAutoInitEnabled(mess, true);
|
||||
return mess
|
||||
} catch (error) {
|
||||
console.error('Failed to initialize Firebase:', error);
|
||||
@@ -58,8 +41,10 @@ export const requestPermission = async () => {
|
||||
}
|
||||
return false
|
||||
}
|
||||
} else if (Platform.OS === 'ios') {
|
||||
const { status } = await Notifications.requestPermissionsAsync();
|
||||
return status === 'granted';
|
||||
}
|
||||
return true
|
||||
} catch (err) {
|
||||
console.warn('Error requesting notification permissions:', err);
|
||||
}
|
||||
@@ -67,8 +52,8 @@ export const requestPermission = async () => {
|
||||
|
||||
export const getToken = async () => {
|
||||
try {
|
||||
await initializeFirebase();
|
||||
const token = await messaging().getToken();
|
||||
const mess = await initializeFirebase();
|
||||
const token = await mess?.getToken();
|
||||
return token;
|
||||
} catch (error) {
|
||||
console.error("Error getting token:", error);
|
||||
@@ -79,8 +64,6 @@ export const useNotification = () => {
|
||||
useEffect(() => {
|
||||
const initializeAndSetup = async () => {
|
||||
try {
|
||||
// await initializeFirebase();
|
||||
// await requestPermission();
|
||||
await getToken();
|
||||
} catch (error) {
|
||||
console.error('Failed to setup notifications:', error);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "mobile-darmasaba",
|
||||
"main": "expo-router/entry",
|
||||
"main": "index.js",
|
||||
"version": "1.0.0",
|
||||
"scripts": {
|
||||
"start": "expo start",
|
||||
@@ -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",
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { ConstEnv } from '@/constants/ConstEnv';
|
||||
import { apiRegisteredToken, apiUnregisteredToken } from '@/lib/api';
|
||||
import { getToken, requestPermission } from '@/lib/useNotification';
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
@@ -41,13 +42,13 @@ export default function AuthProvider({ children }: { children: ReactNode }): Rea
|
||||
|
||||
const decryptToken = (async (token: string) => {
|
||||
var C = require("crypto-js");
|
||||
var Decrypted = C.AES.decrypt(token, "your password");
|
||||
var Decrypted = C.AES.decrypt(token, ConstEnv.pass_encrypt);
|
||||
var result = Decrypted.toString(C.enc.Utf8);
|
||||
return result
|
||||
})
|
||||
|
||||
const encryptToken = (async (token: string) => {
|
||||
var result = CryptoES.AES.encrypt(token, "your password").toString();
|
||||
var result = CryptoES.AES.encrypt(token, ConstEnv.pass_encrypt).toString();
|
||||
return result
|
||||
})
|
||||
|
||||
@@ -56,6 +57,7 @@ export default function AuthProvider({ children }: { children: ReactNode }): Rea
|
||||
const permission = await requestPermission()
|
||||
if (permission) {
|
||||
try {
|
||||
// COMING SOON
|
||||
if (Platform.OS === 'android') {
|
||||
const tokenDevice = await getToken()
|
||||
const register = await apiRegisteredToken({ user: hasil, token: String(tokenDevice) })
|
||||
@@ -78,8 +80,9 @@ export default function AuthProvider({ children }: { children: ReactNode }): Rea
|
||||
const signOut = useCallback(async () => {
|
||||
try {
|
||||
const hasil = await decryptToken(String(tokenRef.current))
|
||||
const token = await getToken()
|
||||
// COMING SOON
|
||||
if (Platform.OS === 'android') {
|
||||
const token = await getToken()
|
||||
const response = await apiUnregisteredToken({ user: hasil, token: String(token) })
|
||||
}
|
||||
} catch (error) {
|
||||
|
||||
Reference in New Issue
Block a user