Compare commits
49 Commits
amalia/01-
...
amalia/19-
| Author | SHA1 | Date | |
|---|---|---|---|
| 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 | |||
| da89673271 | |||
| d052b7a0d4 | |||
| 9370aac9a3 | |||
| 1298ec079a | |||
| 323d31250b | |||
| 5db712d4b9 | |||
| c119d3e775 | |||
| 1e2069ca42 | |||
| 1d555bf950 | |||
| 45d08e1df5 | |||
| 7470e8a9c2 | |||
| 308fda6920 | |||
| 7ad846ff9c | |||
| 92564373fe | |||
| 9a5765f0d0 | |||
| 6c84881186 | |||
| 2d24050b64 | |||
| fafb52d87c | |||
| b7c44109a1 | |||
| c54f4c9fda |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -32,6 +32,9 @@ yarn-error.*
|
||||
# local env files
|
||||
.env*.local
|
||||
|
||||
#env
|
||||
.env
|
||||
|
||||
# typescript
|
||||
*.tsbuildinfo
|
||||
|
||||
|
||||
72
app.config.js
Normal file
72
app.config.js
Normal file
@@ -0,0 +1,72 @@
|
||||
import 'dotenv/config';
|
||||
|
||||
export default {
|
||||
expo: {
|
||||
name: "mobile-darmasaba",
|
||||
slug: "mobile-darmasaba",
|
||||
version: "1.0.0",
|
||||
jsEngine: "jsc",
|
||||
orientation: "portrait",
|
||||
icon: "./assets/images/icon.png",
|
||||
scheme: "myapp",
|
||||
userInterfaceStyle: "automatic",
|
||||
newArchEnabled: false,
|
||||
ios: {
|
||||
supportsTablet: true,
|
||||
bundleIdentifier: "mobiledarmasaba.app",
|
||||
infoPlist: {
|
||||
ITSAppUsesNonExemptEncryption: false
|
||||
},
|
||||
googleServicesFile: "./ios/mobiledarmasaba/GoogleService-Info.plist"
|
||||
},
|
||||
android: {
|
||||
package: "mobiledarmasaba.app",
|
||||
adaptiveIcon: {
|
||||
foregroundImage: "./assets/images/splash-icon.png",
|
||||
backgroundColor: "#ffffff"
|
||||
},
|
||||
googleServicesFile: "./google-services.json"
|
||||
},
|
||||
web: {
|
||||
bundler: "metro",
|
||||
output: "static",
|
||||
favicon: "./assets/images/favicon.png"
|
||||
},
|
||||
plugins: [
|
||||
"expo-router",
|
||||
[
|
||||
"expo-splash-screen",
|
||||
{
|
||||
image: "./assets/images/splash-icon.png",
|
||||
imageWidth: 200,
|
||||
resizeMode: "contain",
|
||||
backgroundColor: "#ffffff"
|
||||
}
|
||||
],
|
||||
"expo-font",
|
||||
"expo-image-picker",
|
||||
"expo-web-browser",
|
||||
[
|
||||
"@react-native-firebase/app",
|
||||
{
|
||||
ios: {
|
||||
googleServicesFile: "./ios/mobiledarmasaba/GoogleService-Info.plist"
|
||||
}
|
||||
}
|
||||
]
|
||||
],
|
||||
experiments: {
|
||||
typedRoutes: true
|
||||
},
|
||||
extra: {
|
||||
router: {},
|
||||
eas: {
|
||||
projectId: "cfe34fb8-da8c-4004-b5c6-29d07df75cf2"
|
||||
},
|
||||
URL_API: process.env.URL_API,
|
||||
URL_OTP: process.env.URL_OTP,
|
||||
URL_STORAGE : process.env.URL_STORAGE,
|
||||
URL_FIREBASE_DB : process.env.URL_FIREBASE_DB
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -13,8 +13,10 @@ import { apiReadOneNotification } from "@/lib/api";
|
||||
import { pushToPage } from "@/lib/pushToPage";
|
||||
import store from "@/lib/store";
|
||||
import { useAuthSession } from "@/providers/AuthProvider";
|
||||
import firebase from '@react-native-firebase/app';
|
||||
import { Redirect, router, Stack } from "expo-router";
|
||||
import AsyncStorage from "@react-native-async-storage/async-storage";
|
||||
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";
|
||||
import { Easing, Notifier } from 'react-native-notifier';
|
||||
@@ -22,11 +24,14 @@ import { Provider } from "react-redux";
|
||||
|
||||
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)
|
||||
@@ -34,25 +39,46 @@ export default function RootLayout() {
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const unsubscribe = firebase.app().messaging().onMessage(async remoteMessage => {
|
||||
const checkNavigation = async () => {
|
||||
const navData = await AsyncStorage.getItem('navigateOnOpen');
|
||||
if (navData) {
|
||||
const { screen, content } = JSON.parse(navData);
|
||||
await AsyncStorage.removeItem('navigateOnOpen'); // reset
|
||||
pushToPage(screen, content)
|
||||
}
|
||||
};
|
||||
|
||||
checkNavigation();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
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) {
|
||||
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) {
|
||||
|
||||
@@ -21,7 +21,8 @@ export default function CreateAnnouncement() {
|
||||
const { token, decryptToken } = useAuthSession()
|
||||
const [disableBtn, setDisableBtn] = useState(true);
|
||||
const [modalDivisi, setModalDivisi] = useState(false);
|
||||
const [divisionMember, setDivisionMember] = useState<any>([]);
|
||||
const [divisionMember, setDivisionMember] = useState<any>([])
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [dataForm, setDataForm] = useState({
|
||||
title: "",
|
||||
desc: "",
|
||||
@@ -66,6 +67,7 @@ export default function CreateAnnouncement() {
|
||||
|
||||
async function handleCreate() {
|
||||
try {
|
||||
setLoading(true)
|
||||
const hasil = await decryptToken(String(token?.current))
|
||||
const response = await apiCreateAnnouncement({
|
||||
data: { ...dataForm, user: hasil, groups: divisionMember },
|
||||
@@ -77,6 +79,8 @@ export default function CreateAnnouncement() {
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,7 +99,7 @@ export default function CreateAnnouncement() {
|
||||
headerTitleAlign: "center",
|
||||
headerRight: () => (
|
||||
<ButtonSaveHeader
|
||||
disable={disableBtn}
|
||||
disable={disableBtn || loading ? true : false}
|
||||
category="create"
|
||||
onPress={() => {
|
||||
divisionMember.length == 0
|
||||
@@ -145,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, Styles.mv05]}>
|
||||
<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>
|
||||
))
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@ export default function EditAnnouncement() {
|
||||
const [modalDivisi, setModalDivisi] = useState(false);
|
||||
const [disableBtn, setDisableBtn] = useState(true);
|
||||
const [dataMember, setDataMember] = useState<any>([]);
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [dataForm, setDataForm] = useState({
|
||||
title: "",
|
||||
desc: "",
|
||||
@@ -109,6 +110,7 @@ export default function EditAnnouncement() {
|
||||
|
||||
async function handleEdit() {
|
||||
try {
|
||||
setLoading(true)
|
||||
const hasil = await decryptToken(String(token?.current))
|
||||
const response = await apiEditAnnouncement({
|
||||
...dataForm, user: hasil, groups: dataMember,
|
||||
@@ -120,6 +122,8 @@ export default function EditAnnouncement() {
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -138,7 +142,7 @@ export default function EditAnnouncement() {
|
||||
headerTitleAlign: "center",
|
||||
headerRight: () => (
|
||||
<ButtonSaveHeader
|
||||
disable={disableBtn}
|
||||
disable={disableBtn || loading ? true : false}
|
||||
category="update"
|
||||
onPress={() => {
|
||||
dataMember.length == 0
|
||||
@@ -189,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, Styles.mv05]}>
|
||||
<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) => {
|
||||
|
||||
@@ -30,6 +30,7 @@ export default function EditBanner() {
|
||||
const [title, setTitle] = useState("");
|
||||
const [error, setError] = useState(false);
|
||||
const [imgForm, setImgForm] = useState<any>();
|
||||
const [loading, setLoading] = useState(false)
|
||||
|
||||
const pickImageAsync = async () => {
|
||||
let result = await ImagePicker.launchImageLibraryAsync({
|
||||
@@ -42,8 +43,6 @@ export default function EditBanner() {
|
||||
if (!result.canceled) {
|
||||
setSelectedImage(result.assets[0].uri);
|
||||
setImgForm(result.assets[0]);
|
||||
} else {
|
||||
alert("Tidak ada gambar yang dipilih");
|
||||
}
|
||||
};
|
||||
|
||||
@@ -71,6 +70,7 @@ export default function EditBanner() {
|
||||
|
||||
const handleUpdateEntity = async () => {
|
||||
try {
|
||||
setLoading(true)
|
||||
const hasil = await decryptToken(String(token?.current));
|
||||
const fd = new FormData();
|
||||
|
||||
@@ -105,6 +105,8 @@ export default function EditBanner() {
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
Toast.show({ type: 'small', text1: 'Gagal mengupdate data', })
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
};
|
||||
|
||||
@@ -122,7 +124,7 @@ export default function EditBanner() {
|
||||
headerTitle: "Edit Banner",
|
||||
headerTitleAlign: "center",
|
||||
headerRight: () => <ButtonSaveHeader
|
||||
disable={title == "" || error ? true : false}
|
||||
disable={title == "" || error || loading ? true : false}
|
||||
onPress={() => { handleUpdateEntity() }}
|
||||
category="update" />,
|
||||
}}
|
||||
|
||||
@@ -29,6 +29,7 @@ export default function CreateBanner() {
|
||||
const [imgForm, setImgForm] = useState<any>();
|
||||
const [title, setTitle] = useState("");
|
||||
const [error, setError] = useState(false);
|
||||
const [loading, setLoading] = useState(false)
|
||||
|
||||
const pickImageAsync = async () => {
|
||||
let result = await ImagePicker.launchImageLibraryAsync({
|
||||
@@ -41,8 +42,6 @@ export default function CreateBanner() {
|
||||
if (result.assets?.[0].uri) {
|
||||
setSelectedImage(result.assets[0].uri);
|
||||
setImgForm(result.assets[0]);
|
||||
} else {
|
||||
alert("Tidak ada gambar yang dipilih");
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -57,32 +56,40 @@ export default function CreateBanner() {
|
||||
}
|
||||
|
||||
const handleCreateEntity = async () => {
|
||||
const hasil = await decryptToken(String(token?.current));
|
||||
const fd = new FormData();
|
||||
try {
|
||||
setLoading(true)
|
||||
const hasil = await decryptToken(String(token?.current));
|
||||
const fd = new FormData();
|
||||
|
||||
fd.append("file", {
|
||||
uri: imgForm.uri,
|
||||
type: imgForm.mimeType,
|
||||
name: imgForm.fileName,
|
||||
} as any);
|
||||
fd.append("file", {
|
||||
uri: imgForm.uri,
|
||||
type: imgForm.mimeType,
|
||||
name: imgForm.fileName,
|
||||
} as any);
|
||||
|
||||
fd.append(
|
||||
"data",
|
||||
JSON.stringify({
|
||||
title,
|
||||
user: hasil,
|
||||
})
|
||||
);
|
||||
|
||||
const createdEntity = await apiCreateBanner(fd);
|
||||
if (createdEntity.success) {
|
||||
Toast.show({ type: 'small', text1: 'Berhasil menambahkan data', })
|
||||
apiGetBanner({ user: hasil }).then((data) =>
|
||||
dispatch(setEntities(data.data))
|
||||
fd.append(
|
||||
"data",
|
||||
JSON.stringify({
|
||||
title,
|
||||
user: hasil,
|
||||
})
|
||||
);
|
||||
router.back();
|
||||
} else {
|
||||
|
||||
const createdEntity = await apiCreateBanner(fd);
|
||||
if (createdEntity.success) {
|
||||
Toast.show({ type: 'small', text1: 'Berhasil menambahkan data', })
|
||||
apiGetBanner({ user: hasil }).then((data) =>
|
||||
dispatch(setEntities(data.data))
|
||||
);
|
||||
router.back();
|
||||
} else {
|
||||
Toast.show({ type: 'small', text1: 'Gagal menambahkan data', })
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
Toast.show({ type: 'small', text1: 'Gagal menambahkan data', })
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
};
|
||||
|
||||
@@ -101,7 +108,7 @@ export default function CreateBanner() {
|
||||
headerTitleAlign: "center",
|
||||
headerRight: () => (
|
||||
<ButtonSaveHeader
|
||||
disable={title == "" || selectedImage == undefined || error ? true : false}
|
||||
disable={title == "" || selectedImage == undefined || error || loading ? true : false}
|
||||
category="create"
|
||||
onPress={() => {
|
||||
handleCreateEntity();
|
||||
|
||||
@@ -155,7 +155,7 @@ export default function BannerList() {
|
||||
/>
|
||||
<MenuItemRow
|
||||
icon={<MaterialCommunityIcons name="file-eye" color="black" size={25} />}
|
||||
title="Lihat File"
|
||||
title="Lihat / Share"
|
||||
onPress={() => { openFile() }}
|
||||
/>
|
||||
<MenuItemRow
|
||||
|
||||
@@ -10,9 +10,11 @@ import Text from '@/components/Text';
|
||||
import { ColorsStatus } from "@/constants/ColorsStatus";
|
||||
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";
|
||||
@@ -48,7 +50,8 @@ 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(() => {
|
||||
const onValueChange = reference.on('value', snapshot => {
|
||||
@@ -143,7 +146,6 @@ export default function DetailDiscussionGeneral() {
|
||||
:
|
||||
<BorderBottomItem
|
||||
descEllipsize={false}
|
||||
width={55}
|
||||
borderType="bottom"
|
||||
icon={
|
||||
<View style={[Styles.iconContent, ColorsStatus.lightGreen]}>
|
||||
@@ -180,7 +182,6 @@ 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" />
|
||||
@@ -197,7 +198,7 @@ export default function DetailDiscussionGeneral() {
|
||||
</ScrollView>
|
||||
<KeyboardAvoidingView
|
||||
behavior={Platform.OS === 'ios' ? 'padding' : undefined}
|
||||
keyboardVerticalOffset={110}
|
||||
keyboardVerticalOffset={headerHeight}
|
||||
>
|
||||
<View style={[
|
||||
Styles.contentItemCenter,
|
||||
|
||||
@@ -31,6 +31,7 @@ export default function AddMemberDiscussionDetail() {
|
||||
const [idGroup, setIdGroup] = useState('')
|
||||
const [selectMember, setSelectMember] = useState<any[]>([])
|
||||
const [search, setSearch] = useState('')
|
||||
const [loading, setLoading] = useState(false)
|
||||
|
||||
async function handleLoad() {
|
||||
try {
|
||||
@@ -70,15 +71,21 @@ export default function AddMemberDiscussionDetail() {
|
||||
|
||||
async function handleAddMember() {
|
||||
try {
|
||||
setLoading(true)
|
||||
const hasil = await decryptToken(String(token?.current))
|
||||
const response = await apiAddMemberDiscussionGeneral({ id: id, data: { user: hasil, member: selectMember } })
|
||||
if (response.success) {
|
||||
Toast.show({ type: 'small', text1: 'Berhasil menambahkan anggota', })
|
||||
dispatch(setUpdateDiscussionGeneralDetail(!update))
|
||||
router.back()
|
||||
} else {
|
||||
Toast.show({ type: 'small', text1: response.message, })
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
Toast.show({ type: 'small', text1: 'Gagal menambahkan anggota', })
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,7 +100,7 @@ export default function AddMemberDiscussionDetail() {
|
||||
headerRight: () => (
|
||||
<ButtonSaveHeader
|
||||
category="update"
|
||||
disable={selectMember.length > 0 ? false : true}
|
||||
disable={selectMember.length == 0 || loading ? true : false}
|
||||
onPress={() => {
|
||||
handleAddMember()
|
||||
}}
|
||||
@@ -149,7 +156,7 @@ export default function AddMemberDiscussionDetail() {
|
||||
</View>
|
||||
</View>
|
||||
{
|
||||
selectMember.some((i: any) => i.idUser == item.id) && <AntDesign name="check" size={20} />
|
||||
selectMember.some((i: any) => i.idUser == item.id) && <AntDesign name="check" size={20} color={'black'}/>
|
||||
}
|
||||
</Pressable>
|
||||
)
|
||||
|
||||
@@ -31,6 +31,7 @@ export default function CreateDiscussionGeneral() {
|
||||
const [isSelect, setSelect] = useState(false);
|
||||
const entitiesMember = useSelector((state: any) => state.memberChoose)
|
||||
const update = useSelector((state: any) => state.discussionGeneralDetailUpdate)
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [dataForm, setDataForm] = useState({
|
||||
idGroup: "",
|
||||
title: "",
|
||||
@@ -95,6 +96,7 @@ export default function CreateDiscussionGeneral() {
|
||||
|
||||
async function handleCreate() {
|
||||
try {
|
||||
setLoading(true)
|
||||
const hasil = await decryptToken(String(token?.current))
|
||||
const response = await apiCreateDiscussionGeneral({
|
||||
data: { ...dataForm, user: hasil, member: entitiesMember },
|
||||
@@ -110,6 +112,8 @@ export default function CreateDiscussionGeneral() {
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
Toast.show({ type: 'small', text1: 'Terjadi kesalahan', })
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -127,7 +131,7 @@ export default function CreateDiscussionGeneral() {
|
||||
headerRight: () => (
|
||||
<ButtonSaveHeader
|
||||
category="create"
|
||||
disable={disableBtn}
|
||||
disable={disableBtn || loading ? true : false}
|
||||
onPress={() => {
|
||||
entitiesMember.length == 0
|
||||
? Toast.show({ type: 'small', text1: 'Anda belum memilih anggota', })
|
||||
|
||||
@@ -16,6 +16,7 @@ export default function EditDiscussionGeneral() {
|
||||
const { id } = useLocalSearchParams<{ id: string }>();
|
||||
const [disableBtn, setDisableBtn] = useState(false)
|
||||
const dispatch = useDispatch()
|
||||
const [loading, setLoading] = useState(false)
|
||||
const update = useSelector((state: any) => state.discussionGeneralDetailUpdate)
|
||||
const [dataForm, setDataForm] = useState({
|
||||
title: "",
|
||||
@@ -80,6 +81,7 @@ export default function EditDiscussionGeneral() {
|
||||
|
||||
async function handleEdit() {
|
||||
try {
|
||||
setLoading(true)
|
||||
const hasil = await decryptToken(String(token?.current));
|
||||
const response = await apiEditDiscussionGeneral({ user: hasil, title: dataForm.title, desc: dataForm.desc }, id);
|
||||
if (response.success) {
|
||||
@@ -89,6 +91,9 @@ export default function EditDiscussionGeneral() {
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
Toast.show({ type: 'small', text1: 'Terjadi kesalahan', })
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,7 +112,7 @@ export default function EditDiscussionGeneral() {
|
||||
headerTitleAlign: "center",
|
||||
headerRight: () => (
|
||||
<ButtonSaveHeader
|
||||
disable={disableBtn}
|
||||
disable={disableBtn || loading ? true : false}
|
||||
category="update"
|
||||
onPress={() => { handleEdit() }}
|
||||
/>
|
||||
|
||||
@@ -31,6 +31,7 @@ export default function AddMemberCalendarEvent() {
|
||||
const [selectMember, setSelectMember] = useState<any[]>([])
|
||||
const [search, setSearch] = useState('')
|
||||
const [idCalendar, setIdCalendar] = useState('')
|
||||
const [loading, setLoading] = useState(false)
|
||||
|
||||
async function handleLoadOldMember() {
|
||||
try {
|
||||
@@ -78,6 +79,7 @@ export default function AddMemberCalendarEvent() {
|
||||
|
||||
async function handleAddMember() {
|
||||
try {
|
||||
setLoading(true)
|
||||
const hasil = await decryptToken(String(token?.current))
|
||||
const response = await apiAddMemberCalendar({ id: idCalendar, data: { user: hasil, member: selectMember } })
|
||||
if (response.success) {
|
||||
@@ -90,6 +92,8 @@ export default function AddMemberCalendarEvent() {
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
Toast.show({ type: 'small', text1: 'Terjadi kesalahan', })
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -104,7 +108,7 @@ export default function AddMemberCalendarEvent() {
|
||||
headerRight: () => (
|
||||
<ButtonSaveHeader
|
||||
category="update"
|
||||
disable={selectMember.length > 0 ? false : true}
|
||||
disable={selectMember.length == 0 || loading ? true : false}
|
||||
onPress={() => {
|
||||
handleAddMember()
|
||||
}}
|
||||
@@ -159,7 +163,7 @@ export default function AddMemberCalendarEvent() {
|
||||
</View>
|
||||
</View>
|
||||
{
|
||||
selectMember.some((i: any) => i.idUser == item.id) && <AntDesign name="check" size={20} />
|
||||
selectMember.some((i: any) => i.idUser == item.id) && <AntDesign name="check" size={20} color={'black'}/>
|
||||
}
|
||||
</Pressable>
|
||||
)
|
||||
|
||||
@@ -21,6 +21,7 @@ export default function EditEventCalendar() {
|
||||
const [isSelect, setSelect] = useState(false)
|
||||
const { id, detail } = useLocalSearchParams<{ id: string, detail: string }>()
|
||||
const [idCalendar, setIdCalendar] = useState('')
|
||||
const [loading, setLoading] = useState(false)
|
||||
|
||||
const [error, setError] = useState({
|
||||
title: false,
|
||||
@@ -140,6 +141,7 @@ export default function EditEventCalendar() {
|
||||
|
||||
async function handleUpdate() {
|
||||
try {
|
||||
setLoading(true)
|
||||
const hasil = await decryptToken(String(token?.current))
|
||||
const response = await apiUpdateCalendar({ data: { ...data, user: hasil }, id: idCalendar })
|
||||
if (response.success) {
|
||||
@@ -150,7 +152,9 @@ export default function EditEventCalendar() {
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
Toast.show({ type: 'small', text1: 'Gagal mengubah acara', })
|
||||
Toast.show({ type: 'small', text1: 'Terjadi kesalahan', })
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -164,7 +168,7 @@ export default function EditEventCalendar() {
|
||||
headerTitleAlign: 'center',
|
||||
headerRight: () =>
|
||||
<ButtonSaveHeader
|
||||
disable={Object.values(error).some((val) => val == true) || data.title == "" || data.dateStart == "" || data.timeStart == "" || data.timeEnd == "" || data.repeatEventTyper == ""}
|
||||
disable={Object.values(error).some((val) => val == true) || data.title == "" || data.dateStart == "" || data.timeStart == "" || data.timeEnd == "" || data.repeatEventTyper == "" || loading}
|
||||
category="update-calendar"
|
||||
onPress={() => {
|
||||
handleUpdate()
|
||||
|
||||
@@ -31,6 +31,7 @@ export default function CreateCalendarAddMember() {
|
||||
const update = useSelector((state: any) => state.calendarCreate)
|
||||
const dispatch = useDispatch()
|
||||
const updateRefresh = useSelector((state: any) => state.calendarUpdate)
|
||||
const [loading, setLoading] = useState(false)
|
||||
|
||||
|
||||
|
||||
@@ -58,6 +59,7 @@ export default function CreateCalendarAddMember() {
|
||||
|
||||
async function handleAddMember() {
|
||||
try {
|
||||
setLoading(true)
|
||||
const hasil = await decryptToken(String(token?.current))
|
||||
const response = await apiCreateCalendar({ data: { ...update, user: hasil, idDivision: id, member: selectMember } })
|
||||
if (response.success) {
|
||||
@@ -80,7 +82,9 @@ export default function CreateCalendarAddMember() {
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
Toast.show({ type: 'small', text1: 'Gagal membuat acara', })
|
||||
Toast.show({ type: 'small', text1: 'Terjadi kesalahan', })
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,7 +98,7 @@ export default function CreateCalendarAddMember() {
|
||||
headerRight: () => (
|
||||
<ButtonSaveHeader
|
||||
category="create"
|
||||
disable={selectMember.length > 0 ? false : true}
|
||||
disable={selectMember.length == 0 || loading ? true : false}
|
||||
onPress={() => { handleAddMember() }}
|
||||
/>
|
||||
)
|
||||
@@ -142,7 +146,7 @@ export default function CreateCalendarAddMember() {
|
||||
</View>
|
||||
</View>
|
||||
{
|
||||
selectMember.some((i: any) => i.idUser == item.idUser) && <AntDesign name="check" size={20} />
|
||||
selectMember.some((i: any) => i.idUser == item.idUser) && <AntDesign name="check" size={20} color={'black'}/>
|
||||
}
|
||||
</Pressable>
|
||||
)
|
||||
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
View
|
||||
} from "react-native";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { useHeaderHeight } from '@react-navigation/elements';
|
||||
|
||||
export default function CalendarDivisionCreate() {
|
||||
const { id } = useLocalSearchParams<{ id: string }>()
|
||||
@@ -24,6 +25,7 @@ export default function CalendarDivisionCreate() {
|
||||
const [isSelect, setSelect] = useState(false)
|
||||
const update = useSelector((state: any) => state.calendarCreate)
|
||||
const dispatch = useDispatch()
|
||||
const headerHeight = useHeaderHeight();
|
||||
const [error, setError] = useState({
|
||||
title: false,
|
||||
dateStart: false,
|
||||
@@ -145,7 +147,7 @@ export default function CalendarDivisionCreate() {
|
||||
/>
|
||||
<KeyboardAvoidingView
|
||||
behavior={Platform.OS === 'ios' ? 'padding' : undefined}
|
||||
keyboardVerticalOffset={110}
|
||||
keyboardVerticalOffset={headerHeight}
|
||||
>
|
||||
<ScrollView>
|
||||
<View style={[Styles.p15]}>
|
||||
|
||||
@@ -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,14 +70,16 @@ 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);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
} finally {
|
||||
setLoadingBtn(false)
|
||||
setTimeout(() => {
|
||||
setLoadingBtn(false)
|
||||
}, 500)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,6 +112,7 @@ export default function CalendarDivision() {
|
||||
text={day.text}
|
||||
isSelected={day.isSelected}
|
||||
isSign={sign}
|
||||
onPress={() => setSelected(new Date(today))}
|
||||
/>
|
||||
);
|
||||
},
|
||||
@@ -154,7 +156,7 @@ export default function CalendarDivision() {
|
||||
mode="single"
|
||||
date={selected}
|
||||
month={month}
|
||||
onChange={({ date }) => setSelected(date)}
|
||||
onMonthChange={(month) => setMonth(month)}
|
||||
styles={{
|
||||
selected: Styles.selectedDate,
|
||||
month_label: Styles.cBlack,
|
||||
|
||||
@@ -17,6 +17,7 @@ export default function DiscussionDivisionEdit() {
|
||||
const [data, setData] = useState("");
|
||||
const update = useSelector((state: any) => state.discussionUpdate);
|
||||
const dispatch = useDispatch();
|
||||
const [loading, setLoading] = useState(false)
|
||||
|
||||
async function handleLoad() {
|
||||
try {
|
||||
@@ -38,6 +39,7 @@ export default function DiscussionDivisionEdit() {
|
||||
|
||||
async function handleUpdate() {
|
||||
try {
|
||||
setLoading(true)
|
||||
const hasil = await decryptToken(String(token?.current));
|
||||
const response = await apiEditDiscussion({
|
||||
data: { user: hasil, desc: data },
|
||||
@@ -47,9 +49,14 @@ export default function DiscussionDivisionEdit() {
|
||||
Toast.show({ type: 'small', text1: 'Berhasil mengubah data', })
|
||||
dispatch(setUpdateDiscussion({ ...update, data: !update.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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,7 +75,7 @@ export default function DiscussionDivisionEdit() {
|
||||
headerTitleAlign: "center",
|
||||
headerRight: () => (
|
||||
<ButtonSaveHeader
|
||||
disable={data == ""}
|
||||
disable={data == "" || loading}
|
||||
category="update"
|
||||
onPress={() => {
|
||||
handleUpdate();
|
||||
|
||||
@@ -13,9 +13,11 @@ import {
|
||||
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";
|
||||
@@ -57,8 +59,9 @@ 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();
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
@@ -214,7 +217,6 @@ export default function DiscussionDetail() {
|
||||
:
|
||||
<BorderBottomItem
|
||||
descEllipsize={false}
|
||||
width={55}
|
||||
borderType="bottom"
|
||||
icon={
|
||||
<ImageUser
|
||||
@@ -262,7 +264,6 @@ export default function DiscussionDetail() {
|
||||
dataComment.map((item, index) => (
|
||||
<BorderBottomItem
|
||||
key={index}
|
||||
width={55}
|
||||
borderType="bottom"
|
||||
icon={
|
||||
<ImageUser
|
||||
@@ -283,7 +284,7 @@ export default function DiscussionDetail() {
|
||||
</ScrollView>
|
||||
<KeyboardAvoidingView
|
||||
behavior={Platform.OS === 'ios' ? 'padding' : undefined}
|
||||
keyboardVerticalOffset={110}
|
||||
keyboardVerticalOffset={headerHeight}
|
||||
>
|
||||
<View
|
||||
style={[
|
||||
|
||||
@@ -17,9 +17,11 @@ export default function CreateDiscussionDivision() {
|
||||
const { token, decryptToken } = useAuthSession()
|
||||
const update = useSelector((state: any) => state.discussionUpdate)
|
||||
const dispatch = useDispatch();
|
||||
const [loading, setLoading] = useState(false)
|
||||
|
||||
async function handleCreate() {
|
||||
try {
|
||||
setLoading(true)
|
||||
const hasil = await decryptToken(String(token?.current))
|
||||
const response = await apiCreateDiscussion({ data: { user: hasil, desc, idDivision: id } })
|
||||
if (response.success) {
|
||||
@@ -32,6 +34,8 @@ export default function CreateDiscussionDivision() {
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
Toast.show({ type: 'small', text1: 'Terjadi kesalahan', })
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,7 +47,7 @@ export default function CreateDiscussionDivision() {
|
||||
headerTitle: 'Tambah Diskusi',
|
||||
headerTitleAlign: 'center',
|
||||
headerRight: () => <ButtonSaveHeader
|
||||
disable={desc == ""}
|
||||
disable={desc == "" || loading}
|
||||
category="create"
|
||||
onPress={() => {
|
||||
handleCreate()
|
||||
|
||||
@@ -138,7 +138,6 @@ export default function DiscussionDivision() {
|
||||
return (
|
||||
<BorderBottomItem
|
||||
key={index}
|
||||
width={55}
|
||||
onPress={() => { router.push(`./discussion/${item.id}`) }}
|
||||
borderType="bottom"
|
||||
icon={
|
||||
@@ -174,7 +173,6 @@ export default function DiscussionDivision() {
|
||||
// data.map((item, index) => (
|
||||
// <BorderBottomItem
|
||||
// key={index}
|
||||
// width={55}
|
||||
// onPress={() => { router.push(`./discussion/${item.id}`) }}
|
||||
// borderType="bottom"
|
||||
// icon={
|
||||
|
||||
@@ -32,6 +32,7 @@ export default function TaskDivisionAddFile() {
|
||||
const [loadingCheck, setLoadingCheck] = useState(false);
|
||||
const dispatch = useDispatch();
|
||||
const update = useSelector((state: any) => state.taskUpdate);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const pickDocumentAsync = async () => {
|
||||
let result = await DocumentPicker.getDocumentAsync({
|
||||
@@ -90,6 +91,7 @@ export default function TaskDivisionAddFile() {
|
||||
|
||||
async function handleAddFile() {
|
||||
try {
|
||||
setLoading(true)
|
||||
const hasil = await decryptToken(String(token?.current));
|
||||
const fd = new FormData();
|
||||
|
||||
@@ -119,6 +121,8 @@ export default function TaskDivisionAddFile() {
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
Toast.show({ type: 'small', text1: 'Terjadi kesalahan', })
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -138,7 +142,7 @@ export default function TaskDivisionAddFile() {
|
||||
headerRight: () => (
|
||||
<ButtonSaveHeader
|
||||
category="create"
|
||||
disable={fileForm.length == 0 ? true : false}
|
||||
disable={fileForm.length == 0 || loading ? true : false}
|
||||
onPress={() => { handleAddFile() }}
|
||||
/>
|
||||
),
|
||||
@@ -171,6 +175,9 @@ export default function TaskDivisionAddFile() {
|
||||
{
|
||||
loadingCheck && <ActivityIndicator size="small" />
|
||||
}
|
||||
{
|
||||
loading && <ActivityIndicator size="large" />
|
||||
}
|
||||
</View>
|
||||
</ScrollView>
|
||||
<DrawerBottom animation="slide" isVisible={isModal} setVisible={setModal} title="Menu">
|
||||
|
||||
@@ -30,6 +30,7 @@ export default function AddMemberTask() {
|
||||
const [data, setData] = useState<Props[]>([])
|
||||
const [selectMember, setSelectMember] = useState<any[]>([])
|
||||
const [search, setSearch] = useState('')
|
||||
const [loading, setLoading] = useState(false)
|
||||
|
||||
async function handleLoadOldMember() {
|
||||
try {
|
||||
@@ -72,6 +73,7 @@ export default function AddMemberTask() {
|
||||
|
||||
async function handleAddMember() {
|
||||
try {
|
||||
setLoading(true)
|
||||
const hasil = await decryptToken(String(token?.current))
|
||||
const response = await apiAddMemberTask({ id: detail, data: { user: hasil, member: selectMember, idDivision: id } })
|
||||
if (response.success) {
|
||||
@@ -83,7 +85,9 @@ export default function AddMemberTask() {
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
Toast.show({ type: 'small', text1: 'Gagal menambahkan anggota', })
|
||||
Toast.show({ type: 'small', text1: 'Terjadi kesalahan', })
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,7 +102,7 @@ export default function AddMemberTask() {
|
||||
headerRight: () => (
|
||||
<ButtonSaveHeader
|
||||
category="update"
|
||||
disable={selectMember.length > 0 ? false : true}
|
||||
disable={selectMember.length == 0 || loading ? true : false}
|
||||
onPress={() => {
|
||||
handleAddMember()
|
||||
}}
|
||||
@@ -154,7 +158,7 @@ export default function AddMemberTask() {
|
||||
</View>
|
||||
</View>
|
||||
{
|
||||
selectMember.some((i: any) => i.idUser == item.idUser) && <AntDesign name="check" size={20} />
|
||||
selectMember.some((i: any) => i.idUser == item.idUser) && <AntDesign name="check" size={20} color={'black'}/>
|
||||
}
|
||||
</Pressable>
|
||||
)
|
||||
|
||||
@@ -8,6 +8,8 @@ import { setUpdateTask } from "@/lib/taskUpdate";
|
||||
import { useAuthSession } from "@/providers/AuthProvider";
|
||||
import dayjs from "dayjs";
|
||||
import { router, Stack, useLocalSearchParams } from "expo-router";
|
||||
import 'intl';
|
||||
import 'intl/locale-data/jsonp/id';
|
||||
import { useEffect, useState } from "react";
|
||||
import {
|
||||
KeyboardAvoidingView, Platform, SafeAreaView,
|
||||
@@ -17,6 +19,7 @@ import {
|
||||
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();
|
||||
@@ -24,6 +27,8 @@ export default function TaskDivisionAddTask() {
|
||||
const update = useSelector((state: any) => state.taskUpdate);
|
||||
const { id, detail } = useLocalSearchParams<{ id: string; detail: string }>();
|
||||
const [disable, setDisable] = useState(true);
|
||||
const [loading, setLoading] = useState(false)
|
||||
const headerHeight = useHeaderHeight();
|
||||
const [range, setRange] = useState<{
|
||||
startDate: DateType;
|
||||
endDate: DateType;
|
||||
@@ -73,6 +78,7 @@ export default function TaskDivisionAddTask() {
|
||||
|
||||
async function handleCreate() {
|
||||
try {
|
||||
setLoading(true)
|
||||
const hasil = await decryptToken(String(token?.current));
|
||||
const response = await apiCreateTaskTugas({
|
||||
data: {
|
||||
@@ -93,7 +99,9 @@ export default function TaskDivisionAddTask() {
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
Toast.show({ type: 'small', text1: 'Gagal menambah data', })
|
||||
Toast.show({ type: 'small', text1: 'Terjadi kesalahan', })
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,7 +121,7 @@ export default function TaskDivisionAddTask() {
|
||||
headerRight: () => (
|
||||
<ButtonSaveHeader
|
||||
category="create"
|
||||
disable={disable}
|
||||
disable={disable || loading}
|
||||
onPress={() => {
|
||||
handleCreate();
|
||||
}}
|
||||
@@ -123,7 +131,7 @@ export default function TaskDivisionAddTask() {
|
||||
/>
|
||||
<KeyboardAvoidingView
|
||||
behavior={Platform.OS === 'ios' ? 'padding' : undefined}
|
||||
keyboardVerticalOffset={110}
|
||||
keyboardVerticalOffset={headerHeight}
|
||||
>
|
||||
<ScrollView>
|
||||
<View style={[Styles.p15, Styles.mb100]}>
|
||||
@@ -137,6 +145,13 @@ export default function TaskDivisionAddTask() {
|
||||
selected: Styles.selectedDate,
|
||||
selected_label: Styles.cWhite,
|
||||
range_fill: Styles.selectRangeDate,
|
||||
month_label: Styles.cBlack,
|
||||
month_selector_label: Styles.cBlack,
|
||||
year_label: Styles.cBlack,
|
||||
year_selector_label: Styles.cBlack,
|
||||
day_label: Styles.cBlack,
|
||||
time_label: Styles.cBlack,
|
||||
weekday_label: Styles.cBlack,
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
|
||||
@@ -19,6 +19,7 @@ export default function TaskDivisionCancel() {
|
||||
const [reason, setReason] = useState("");
|
||||
const [error, setError] = useState(false);
|
||||
const [disable, setDisable] = useState(false);
|
||||
const [loading, setLoading] = useState(false)
|
||||
|
||||
function onValidation(val: string) {
|
||||
setReason(val);
|
||||
@@ -43,6 +44,7 @@ export default function TaskDivisionCancel() {
|
||||
|
||||
async function handleCancel() {
|
||||
try {
|
||||
setLoading(true)
|
||||
const hasil = await decryptToken(String(token?.current));
|
||||
const response = await apiCancelTask(
|
||||
{
|
||||
@@ -60,7 +62,9 @@ export default function TaskDivisionCancel() {
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
Toast.show({ type: 'small', text1: 'Gagal membatalkan kegiatan', })
|
||||
Toast.show({ type: 'small', text1: 'Terjadi kesalahan', })
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,7 +83,7 @@ export default function TaskDivisionCancel() {
|
||||
headerTitleAlign: "center",
|
||||
headerRight: () => (
|
||||
<ButtonSaveHeader
|
||||
disable={disable}
|
||||
disable={disable || loading}
|
||||
category="cancel"
|
||||
onPress={() => {
|
||||
handleCancel();
|
||||
|
||||
@@ -19,6 +19,7 @@ export default function TaskDivisionEdit() {
|
||||
const [disable, setDisable] = useState(false);
|
||||
const dispatch = useDispatch();
|
||||
const update = useSelector((state: any) => state.taskUpdate);
|
||||
const [loading, setLoading] = useState(false)
|
||||
|
||||
async function handleLoad() {
|
||||
try {
|
||||
@@ -61,6 +62,7 @@ export default function TaskDivisionEdit() {
|
||||
|
||||
async function handleUpdate() {
|
||||
try {
|
||||
setLoading(true)
|
||||
const hasil = await decryptToken(String(token?.current));
|
||||
const response = await apiEditTask(
|
||||
{
|
||||
@@ -79,6 +81,8 @@ export default function TaskDivisionEdit() {
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
Toast.show({ type: 'small', text1: 'Terjadi kesalahan', })
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,7 +102,7 @@ export default function TaskDivisionEdit() {
|
||||
headerRight: () => (
|
||||
<ButtonSaveHeader
|
||||
category="update"
|
||||
disable={disable}
|
||||
disable={disable || loading}
|
||||
onPress={() => { handleUpdate() }}
|
||||
/>
|
||||
),
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -38,6 +38,7 @@ export default function CreateTaskDivision() {
|
||||
const [title, setTitle] = useState('')
|
||||
const [error, setError] = useState(false);
|
||||
const [isModal, setModal] = useState(false)
|
||||
const [loading, setLoading] = useState(false)
|
||||
let hitung = 0;
|
||||
|
||||
useEffect(() => {
|
||||
@@ -77,6 +78,7 @@ export default function CreateTaskDivision() {
|
||||
|
||||
async function handleCreate() {
|
||||
try {
|
||||
setLoading(true)
|
||||
const hasil = await decryptToken(String(token?.current))
|
||||
const fd = new FormData()
|
||||
|
||||
@@ -102,6 +104,9 @@ export default function CreateTaskDivision() {
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
Toast.show({ type: 'small', text1: 'Terjadi kesalahan', })
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,7 +126,7 @@ export default function CreateTaskDivision() {
|
||||
headerTitleAlign: "center",
|
||||
headerRight: () => (
|
||||
<ButtonSaveHeader
|
||||
disable={title == "" || entitiesMember.length == 0 || taskCreate.length == 0}
|
||||
disable={title == "" || entitiesMember.length == 0 || taskCreate.length == 0 || loading}
|
||||
category="create"
|
||||
onPress={() => { handleCreate() }}
|
||||
/>
|
||||
|
||||
@@ -25,7 +25,6 @@ export default function AddMemberCreateTask() {
|
||||
const dispatch = useDispatch()
|
||||
const { token, decryptToken } = useAuthSession()
|
||||
const { id } = useLocalSearchParams<{ id: string, detail: string }>()
|
||||
const [dataOld, setDataOld] = useState<Props[]>([])
|
||||
const [data, setData] = useState<Props[]>([])
|
||||
const [selectMember, setSelectMember] = useState<any[]>([])
|
||||
const [search, setSearch] = useState('')
|
||||
@@ -125,7 +124,7 @@ export default function AddMemberCreateTask() {
|
||||
</View>
|
||||
</View>
|
||||
{
|
||||
selectMember.some((i: any) => i.idUser == item.idUser) && <AntDesign name="check" size={20} />
|
||||
selectMember.some((i: any) => i.idUser == item.idUser) && <AntDesign name="check" size={20} color={'black'}/>
|
||||
}
|
||||
</Pressable>
|
||||
)
|
||||
|
||||
@@ -6,6 +6,8 @@ import Styles from "@/constants/Styles";
|
||||
import { setTaskCreate } from "@/lib/taskCreate";
|
||||
import dayjs from "dayjs";
|
||||
import { router, Stack } from "expo-router";
|
||||
import 'intl';
|
||||
import 'intl/locale-data/jsonp/id';
|
||||
import { useEffect, useState } from "react";
|
||||
import {
|
||||
KeyboardAvoidingView,
|
||||
@@ -18,8 +20,10 @@ 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();
|
||||
const dispatch = useDispatch()
|
||||
const [disable, setDisable] = useState(true);
|
||||
const [range, setRange] = useState<{
|
||||
@@ -101,7 +105,7 @@ export default function CreateTaskAddTugas() {
|
||||
/>
|
||||
<KeyboardAvoidingView
|
||||
behavior={Platform.OS === 'ios' ? 'padding' : undefined}
|
||||
keyboardVerticalOffset={110}
|
||||
keyboardVerticalOffset={headerHeight}
|
||||
>
|
||||
<ScrollView>
|
||||
<View style={[Styles.p15, Styles.mb100]}>
|
||||
@@ -115,6 +119,13 @@ export default function CreateTaskAddTugas() {
|
||||
selected: Styles.selectedDate,
|
||||
selected_label: Styles.cWhite,
|
||||
range_fill: Styles.selectRangeDate,
|
||||
month_label: Styles.cBlack,
|
||||
month_selector_label: Styles.cBlack,
|
||||
year_label: Styles.cBlack,
|
||||
year_selector_label: Styles.cBlack,
|
||||
day_label: Styles.cBlack,
|
||||
time_label: Styles.cBlack,
|
||||
weekday_label: Styles.cBlack,
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
|
||||
@@ -6,8 +6,11 @@ import Styles from "@/constants/Styles";
|
||||
import { apiEditTaskTugas, apiGetTaskTugas } from "@/lib/api";
|
||||
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 { useEffect, useState } from "react";
|
||||
import {
|
||||
KeyboardAvoidingView,
|
||||
@@ -21,6 +24,7 @@ import DateTimePicker, { DateType } from "react-native-ui-datepicker";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
|
||||
export default function UpdateProjectTaskDivision() {
|
||||
const headerHeight = useHeaderHeight();
|
||||
const { detail } = useLocalSearchParams<{ detail: string }>();
|
||||
const dispatch = useDispatch();
|
||||
const update = useSelector((state: any) => state.taskUpdate);
|
||||
@@ -29,6 +33,7 @@ export default function UpdateProjectTaskDivision() {
|
||||
startDate: DateType;
|
||||
endDate: DateType;
|
||||
}>({ startDate: undefined, endDate: undefined });
|
||||
const [loadingSubmit, setLoadingSubmit] = useState(false)
|
||||
const [month, setMonth] = useState<any>();
|
||||
const [year, setYear] = useState<any>();
|
||||
const [loading, setLoading] = useState(true);
|
||||
@@ -73,6 +78,7 @@ export default function UpdateProjectTaskDivision() {
|
||||
|
||||
async function handleEdit() {
|
||||
try {
|
||||
setLoadingSubmit(true)
|
||||
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 });
|
||||
if (response.success) {
|
||||
@@ -84,7 +90,9 @@ export default function UpdateProjectTaskDivision() {
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
Toast.show({ type: 'small', text1: 'Gagal mengubah data', })
|
||||
Toast.show({ type: 'small', text1: 'Terjadi kesalahan', })
|
||||
} finally {
|
||||
setLoadingSubmit(false)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,7 +142,7 @@ export default function UpdateProjectTaskDivision() {
|
||||
headerTitleAlign: "center",
|
||||
headerRight: () => (
|
||||
<ButtonSaveHeader
|
||||
disable={disableBtn}
|
||||
disable={disableBtn || loadingSubmit}
|
||||
category="update"
|
||||
onPress={() => {
|
||||
handleEdit()
|
||||
@@ -145,7 +153,7 @@ export default function UpdateProjectTaskDivision() {
|
||||
/>
|
||||
<KeyboardAvoidingView
|
||||
behavior={Platform.OS === 'ios' ? 'padding' : undefined}
|
||||
keyboardVerticalOffset={110}
|
||||
keyboardVerticalOffset={headerHeight}
|
||||
>
|
||||
<ScrollView>
|
||||
<View style={[Styles.p15, Styles.mb100]}>
|
||||
@@ -162,6 +170,13 @@ export default function UpdateProjectTaskDivision() {
|
||||
selected: Styles.selectedDate,
|
||||
selected_label: Styles.cWhite,
|
||||
range_fill: Styles.selectRangeDate,
|
||||
month_label: Styles.cBlack,
|
||||
month_selector_label: Styles.cBlack,
|
||||
year_label: Styles.cBlack,
|
||||
year_selector_label: Styles.cBlack,
|
||||
day_label: Styles.cBlack,
|
||||
time_label: Styles.cBlack,
|
||||
weekday_label: Styles.cBlack,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -31,9 +31,11 @@ export default function AddMemberDivision() {
|
||||
const [search, setSearch] = useState('')
|
||||
const update = useSelector((state: any) => state.divisionUpdate)
|
||||
const dispatch = useDispatch()
|
||||
const [loading, setLoading] = useState(false)
|
||||
|
||||
async function handleLoad() {
|
||||
try {
|
||||
setLoading(true)
|
||||
const hasil = await decryptToken(String(token?.current))
|
||||
const response = await apiGetDivisionOneDetail({ user: hasil, id })
|
||||
setDataOld(response.data.member)
|
||||
@@ -72,6 +74,7 @@ export default function AddMemberDivision() {
|
||||
|
||||
async function handleAddMember() {
|
||||
try {
|
||||
setLoading(true)
|
||||
const hasil = await decryptToken(String(token?.current))
|
||||
const response = await apiAddMemberDivision({ id: id, data: { user: hasil, member: selectMember } })
|
||||
if (response.success) {
|
||||
@@ -83,7 +86,9 @@ export default function AddMemberDivision() {
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
Toast.show({ type: 'small', text1: 'Gagal menambahkan anggota', })
|
||||
Toast.show({ type: 'small', text1: 'Terjadi kesalahan', })
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,7 +103,7 @@ export default function AddMemberDivision() {
|
||||
headerRight: () => (
|
||||
<ButtonSaveHeader
|
||||
category="update"
|
||||
disable={selectMember.length > 0 ? false : true}
|
||||
disable={selectMember.length == 0 || loading ? true : false}
|
||||
onPress={() => {
|
||||
handleAddMember()
|
||||
}}
|
||||
@@ -154,7 +159,7 @@ export default function AddMemberDivision() {
|
||||
</View>
|
||||
</View>
|
||||
{
|
||||
selectMember.some((i: any) => i.idUser == item.id) && <AntDesign name="check" size={20} />
|
||||
selectMember.some((i: any) => i.idUser == item.id) && <AntDesign name="check" size={20} color={'black'}/>
|
||||
}
|
||||
</Pressable>
|
||||
)
|
||||
|
||||
@@ -16,6 +16,7 @@ export default function EditDivision() {
|
||||
const update = useSelector((state: any) => state.divisionUpdate)
|
||||
const { token, decryptToken } = useAuthSession();
|
||||
const { id } = useLocalSearchParams<{ id: string }>();
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [data, setData] = useState({
|
||||
name: "",
|
||||
desc: "",
|
||||
@@ -43,6 +44,7 @@ export default function EditDivision() {
|
||||
|
||||
async function handleEdit() {
|
||||
try {
|
||||
setLoading(true)
|
||||
const hasil = await decryptToken(String(token?.current))
|
||||
const response = await apiEditDivision({ user: hasil, name: data.name, desc: data.desc }, id)
|
||||
if (response.success) {
|
||||
@@ -55,6 +57,8 @@ export default function EditDivision() {
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
Toast.show({ type: 'small', text1: 'Terjadi kesalahan', })
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,7 +77,7 @@ export default function EditDivision() {
|
||||
headerTitleAlign: "center",
|
||||
headerRight: () => (
|
||||
<ButtonSaveHeader
|
||||
disable={error.name}
|
||||
disable={error.name || loading ? true : false}
|
||||
category="update"
|
||||
onPress={() => { handleEdit() }}
|
||||
/>
|
||||
|
||||
@@ -184,7 +184,6 @@ export default function InformationDivision() {
|
||||
dataMember.map((item, index) => {
|
||||
return (
|
||||
<BorderBottomItem
|
||||
width={55}
|
||||
key={index}
|
||||
borderType="bottom"
|
||||
onPress={() => { dataDetail?.isActive && handleChooseMember(item) }}
|
||||
|
||||
@@ -29,6 +29,7 @@ export default function CreateDivisionAddAdmin() {
|
||||
const update = useSelector((state: any) => state.divisionCreate)
|
||||
const updateDivision = useSelector((state: any) => state.divisionUpdate)
|
||||
const dispatch = useDispatch()
|
||||
const [loading, setLoading] = useState(false)
|
||||
|
||||
async function handleLoadMember() {
|
||||
setData(update.member)
|
||||
@@ -48,6 +49,7 @@ export default function CreateDivisionAddAdmin() {
|
||||
|
||||
async function handleAddMember() {
|
||||
try {
|
||||
setLoading(true)
|
||||
dispatch(setFormCreateDivision({ ...update, admin: selectMember }))
|
||||
const hasil = await decryptToken(String(token?.current))
|
||||
const response = await apiCreateDivision({ ...update, user: hasil })
|
||||
@@ -61,7 +63,9 @@ export default function CreateDivisionAddAdmin() {
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
Toast.show({ type: 'small', text1: 'Gagal membuat divisi', })
|
||||
Toast.show({ type: 'small', text1: 'Terjadi kesalahan', })
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,7 +80,7 @@ export default function CreateDivisionAddAdmin() {
|
||||
headerRight: () => (
|
||||
<ButtonSaveHeader
|
||||
category="create"
|
||||
disable={selectMember.length > 0 ? false : true}
|
||||
disable={selectMember.length == 0 || loading ? true : false}
|
||||
onPress={() => {
|
||||
handleAddMember()
|
||||
}}
|
||||
@@ -108,7 +112,7 @@ export default function CreateDivisionAddAdmin() {
|
||||
</View>
|
||||
</View>
|
||||
{
|
||||
selectMember.some((i: any) => i == item.idUser) && <AntDesign name="check" size={20} />
|
||||
selectMember.some((i: any) => i == item.idUser) && <AntDesign name="check" size={20} color={'black'}/>
|
||||
}
|
||||
</Pressable>
|
||||
)
|
||||
|
||||
@@ -121,7 +121,7 @@ export default function CreateDivisionAddMember() {
|
||||
</View>
|
||||
</View>
|
||||
{
|
||||
selectMember.some((i: any) => i.idUser == item.id) && <AntDesign name="check" size={20} />
|
||||
selectMember.some((i: any) => i.idUser == item.id) && <AntDesign name="check" size={20} color={'black'}/>
|
||||
}
|
||||
</Pressable>
|
||||
)
|
||||
|
||||
@@ -8,6 +8,7 @@ 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";
|
||||
@@ -46,7 +47,8 @@ export default function EditProfile() {
|
||||
const [isSelect, setSelect] = useState(false);
|
||||
const [disableBtn, setDisableBtn] = useState(false)
|
||||
const [valChoose, setValChoose] = useState("")
|
||||
const [imgForm, setImgForm] = useState<any>();
|
||||
const [imgForm, setImgForm] = useState<any>()
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [data, setData] = useState<Props>({
|
||||
id: entities.id,
|
||||
name: entities.name,
|
||||
@@ -150,6 +152,7 @@ export default function EditProfile() {
|
||||
|
||||
async function handleEdit() {
|
||||
try {
|
||||
setLoading(true)
|
||||
const hasil = await decryptToken(String(token?.current))
|
||||
const fd = new FormData()
|
||||
|
||||
@@ -179,6 +182,8 @@ export default function EditProfile() {
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
Toast.show({ type: 'small', text1: 'Gagal mengupdate data', })
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -197,7 +202,6 @@ export default function EditProfile() {
|
||||
setSelectedImage(result.assets[0].uri);
|
||||
setImgForm(result.assets[0]);
|
||||
} else {
|
||||
alert("Tidak ada gambar yang dipilih");
|
||||
setErrorImg(false)
|
||||
}
|
||||
};
|
||||
@@ -217,7 +221,7 @@ export default function EditProfile() {
|
||||
headerTitleAlign: "center",
|
||||
headerRight: () => (
|
||||
<ButtonSaveHeader
|
||||
disable={disableBtn}
|
||||
disable={disableBtn || loading ? true : false}
|
||||
category="update"
|
||||
onPress={() => {
|
||||
handleEdit()
|
||||
@@ -226,8 +230,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 ? (
|
||||
@@ -241,16 +245,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: `https://wibu-storage.wibudev.com/api/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";
|
||||
|
||||
@@ -42,14 +42,9 @@ export default function Index() {
|
||||
|
||||
const dispatch = useDispatch()
|
||||
const update = useSelector((state: any) => state.groupUpdate)
|
||||
|
||||
const [data11, setData1] = useState(Array.from({ length: 20 }, (_, i) => `Item ${i}`));
|
||||
|
||||
const renderItem = ({ item }: { item: string }) => (
|
||||
<View style={{ padding: 20, borderBottomWidth: 1, borderColor: '#ccc' }}>
|
||||
<Text>{item}</Text>
|
||||
</View>
|
||||
);
|
||||
const [error, setError] = useState({
|
||||
title: false,
|
||||
});
|
||||
|
||||
|
||||
async function handleEdit() {
|
||||
@@ -109,10 +104,29 @@ export default function Index() {
|
||||
setRefreshing(false)
|
||||
};
|
||||
|
||||
function validationForm(val: any, cat: 'title') {
|
||||
if (cat === 'title') {
|
||||
setTitleChoose(val)
|
||||
if (val == "" || val.length < 3) {
|
||||
setError((prev) => ({ ...prev, title: true }))
|
||||
} else {
|
||||
setError((prev) => ({ ...prev, title: false }))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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"}
|
||||
@@ -130,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}>
|
||||
@@ -207,15 +223,22 @@ export default function Index() {
|
||||
<DrawerBottom animation="none" keyboard height={30} isVisible={isVisibleEdit} setVisible={() => setVisibleEdit(false)} title="Edit Lembaga Desa">
|
||||
<View style={{ flex: 1 }}>
|
||||
<View>
|
||||
<InputForm type="default" placeholder="Nama Lembaga Desa" required label="Lembaga Desa" value={titleChoose} onChange={setTitleChoose} />
|
||||
<InputForm
|
||||
type="default"
|
||||
placeholder="Nama Lembaga Desa"
|
||||
required
|
||||
label="Lembaga Desa"
|
||||
value={titleChoose}
|
||||
error={error.title}
|
||||
errorText="Lembaga Desa tidak boleh kosong & minimal 3 karakter"
|
||||
onChange={(val) => { validationForm(val, 'title') }} />
|
||||
</View>
|
||||
<View>
|
||||
<ButtonForm text="SIMPAN" onPress={() => { handleEdit() }} />
|
||||
<ButtonForm text="SIMPAN" disabled={Object.values(error).some((v) => v == true) || titleChoose == ""} onPress={() => { handleEdit() }} />
|
||||
</View>
|
||||
</View>
|
||||
</DrawerBottom>
|
||||
</View >
|
||||
|
||||
|
||||
</SafeAreaView>
|
||||
)
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import ButtonBackHeader from "@/components/buttonBackHeader";
|
||||
import ImageUser from "@/components/imageNew";
|
||||
import ItemDetailMember from "@/components/itemDetailMember";
|
||||
import LabelStatus from "@/components/labelStatus";
|
||||
import HeaderRightMemberDetail from "@/components/member/headerMemberDetail";
|
||||
import Skeleton from "@/components/skeleton";
|
||||
import Text from "@/components/Text";
|
||||
@@ -102,6 +103,11 @@ export default function MemberDetail() {
|
||||
<View style={[Styles.p15]}>
|
||||
<View style={[Styles.rowSpaceBetween]}>
|
||||
<Text style={[Styles.textDefaultSemiBold]}>Informasi</Text>
|
||||
<LabelStatus
|
||||
size="small"
|
||||
category={data?.isActive ? 'success' : 'error'}
|
||||
text={data?.isActive ? 'AKTIF' : 'TIDAK AKTIF'}
|
||||
/>
|
||||
</View>
|
||||
{
|
||||
loading ?
|
||||
|
||||
@@ -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";
|
||||
@@ -26,6 +27,7 @@ import Toast from "react-native-toast-message";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
|
||||
export default function CreateMember() {
|
||||
const headerHeight = useHeaderHeight();
|
||||
const dispatch = useDispatch()
|
||||
const update = useSelector((state: any) => state.memberUpdate)
|
||||
const { token, decryptToken } = useAuthSession()
|
||||
@@ -41,6 +43,7 @@ export default function CreateMember() {
|
||||
const [disableBtn, setDisableBtn] = useState(true)
|
||||
const [valChoose, setValChoose] = useState("")
|
||||
const [imgForm, setImgForm] = useState<any>()
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [error, setError] = useState({
|
||||
group: false,
|
||||
position: false,
|
||||
@@ -144,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)
|
||||
}
|
||||
}, [])
|
||||
@@ -152,6 +155,7 @@ export default function CreateMember() {
|
||||
|
||||
async function handleCreate() {
|
||||
try {
|
||||
setLoading(true)
|
||||
const hasil = await decryptToken(String(token?.current))
|
||||
const fd = new FormData()
|
||||
|
||||
@@ -169,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))
|
||||
@@ -179,6 +183,9 @@ export default function CreateMember() {
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
Toast.show({ type: 'small', text1: 'Terjadi kesalahan', })
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -194,9 +201,6 @@ export default function CreateMember() {
|
||||
if (!result.canceled) {
|
||||
setSelectedImage(result.assets[0].uri);
|
||||
setImgForm(result.assets[0]);
|
||||
|
||||
} else {
|
||||
alert("Tidak ada gambar yang dipilih");
|
||||
}
|
||||
};
|
||||
|
||||
@@ -215,7 +219,7 @@ export default function CreateMember() {
|
||||
headerTitleAlign: "center",
|
||||
headerRight: () => (
|
||||
<ButtonSaveHeader
|
||||
disable={disableBtn}
|
||||
disable={disableBtn || loading}
|
||||
category="create"
|
||||
onPress={() => { handleCreate() }}
|
||||
/>
|
||||
@@ -223,9 +227,9 @@ export default function CreateMember() {
|
||||
}}
|
||||
/>
|
||||
<KeyboardAvoidingView
|
||||
style={[Styles.h100]}
|
||||
behavior={Platform.OS === 'ios' ? 'padding' : undefined}
|
||||
keyboardVerticalOffset={110}
|
||||
style={[Styles.h100]}
|
||||
behavior={Platform.OS === 'ios' ? 'padding' : undefined}
|
||||
keyboardVerticalOffset={headerHeight}
|
||||
>
|
||||
<ScrollView>
|
||||
<View style={[Styles.p15]}>
|
||||
@@ -233,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>
|
||||
|
||||
@@ -8,6 +8,8 @@ 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";
|
||||
@@ -38,6 +40,7 @@ type Props = {
|
||||
};
|
||||
|
||||
export default function EditMember() {
|
||||
const headerHeight = useHeaderHeight();
|
||||
const dispatch = useDispatch()
|
||||
const update = useSelector((state: any) => state.memberUpdate)
|
||||
const { token, decryptToken } = useAuthSession()
|
||||
@@ -52,6 +55,7 @@ export default function EditMember() {
|
||||
const [disableBtn, setDisableBtn] = useState(false)
|
||||
const [valChoose, setValChoose] = useState("")
|
||||
const [imgForm, setImgForm] = useState<any>();
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [data, setData] = useState<Props>({
|
||||
id: "",
|
||||
name: "",
|
||||
@@ -175,6 +179,7 @@ export default function EditMember() {
|
||||
|
||||
async function handleEdit() {
|
||||
try {
|
||||
setLoading(true)
|
||||
const hasil = await decryptToken(String(token?.current))
|
||||
const fd = new FormData()
|
||||
|
||||
@@ -203,7 +208,9 @@ export default function EditMember() {
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
Toast.show({ type: 'small', text1: 'Gagal mengupdate data', })
|
||||
Toast.show({ type: 'small', text1: 'Terjadi kesalahan', })
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -222,7 +229,6 @@ export default function EditMember() {
|
||||
setSelectedImage(result.assets[0].uri);
|
||||
setImgForm(result.assets[0]);
|
||||
} else {
|
||||
alert("Tidak ada gambar yang dipilih");
|
||||
setErrorImg(false)
|
||||
}
|
||||
};
|
||||
@@ -242,7 +248,7 @@ export default function EditMember() {
|
||||
headerTitleAlign: "center",
|
||||
headerRight: () => (
|
||||
<ButtonSaveHeader
|
||||
disable={disableBtn}
|
||||
disable={disableBtn || loading}
|
||||
category="update"
|
||||
onPress={() => {
|
||||
handleEdit()
|
||||
@@ -255,7 +261,7 @@ export default function EditMember() {
|
||||
<KeyboardAvoidingView
|
||||
style={[Styles.h100]}
|
||||
behavior={Platform.OS === 'ios' ? 'padding' : undefined}
|
||||
keyboardVerticalOffset={110}
|
||||
keyboardVerticalOffset={headerHeight}
|
||||
>
|
||||
<ScrollView>
|
||||
<View style={[Styles.p15, Styles.mb100]}>
|
||||
@@ -263,13 +269,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: `https://wibu-storage.wibudev.com/api/files/${data?.img}` }}
|
||||
style={[Styles.userProfileBig]}
|
||||
onError={() => { setErrorImg(true) }}
|
||||
/>
|
||||
<View style={[Styles.absoluteIconPicker]}>
|
||||
<MaterialCommunityIcons name="image" color={'white'} size={15} />
|
||||
</View>
|
||||
</Pressable>
|
||||
:
|
||||
selectedImage != undefined ? (
|
||||
@@ -283,6 +290,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
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -26,6 +26,7 @@ export default function ProjectAddFile() {
|
||||
const [loadingCheck, setLoadingCheck] = useState(false)
|
||||
const dispatch = useDispatch()
|
||||
const update = useSelector((state: any) => state.projectUpdate)
|
||||
const [loading, setLoading] = useState(false)
|
||||
|
||||
const pickDocumentAsync = async () => {
|
||||
let result = await DocumentPicker.getDocumentAsync({
|
||||
@@ -86,6 +87,7 @@ export default function ProjectAddFile() {
|
||||
|
||||
async function handleAddFile() {
|
||||
try {
|
||||
setLoading(true)
|
||||
const hasil = await decryptToken(String(token?.current))
|
||||
const fd = new FormData();
|
||||
|
||||
@@ -116,6 +118,8 @@ export default function ProjectAddFile() {
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
Toast.show({ type: 'small', text1: 'Terjadi kesalahan', })
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -130,7 +134,7 @@ export default function ProjectAddFile() {
|
||||
headerTitle: 'Tambah File',
|
||||
headerTitleAlign: 'center',
|
||||
headerRight: () => <ButtonSaveHeader
|
||||
disable={fileForm.length == 0 ? true : false}
|
||||
disable={fileForm.length == 0 || loading ? true : false}
|
||||
category="create"
|
||||
onPress={() => { handleAddFile() }} />
|
||||
}}
|
||||
@@ -162,6 +166,9 @@ export default function ProjectAddFile() {
|
||||
{
|
||||
loadingCheck && <ActivityIndicator size="small" />
|
||||
}
|
||||
{
|
||||
loading && <ActivityIndicator size="large" />
|
||||
}
|
||||
</View>
|
||||
</ScrollView>
|
||||
<DrawerBottom animation="slide" isVisible={isModal} setVisible={setModal} title="Menu">
|
||||
|
||||
@@ -31,6 +31,7 @@ export default function AddMemberProject() {
|
||||
const [idGroup, setIdGroup] = useState('')
|
||||
const [selectMember, setSelectMember] = useState<any[]>([])
|
||||
const [search, setSearch] = useState('')
|
||||
const [loading, setLoading] = useState(false)
|
||||
|
||||
async function handleLoad() {
|
||||
try {
|
||||
@@ -43,6 +44,7 @@ export default function AddMemberProject() {
|
||||
setData(responsemember.data.filter((i: any) => i.idUserRole != 'supadmin'))
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
Toast.show({ type: 'small', text1: 'Terjadi kesalahan', })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,6 +75,7 @@ export default function AddMemberProject() {
|
||||
|
||||
async function handleAddMember() {
|
||||
try {
|
||||
setLoading(true)
|
||||
const hasil = await decryptToken(String(token?.current))
|
||||
const response = await apiAddMemberProject({ id: id, data: { user: hasil, member: selectMember } })
|
||||
if (response.success) {
|
||||
@@ -82,6 +85,9 @@ export default function AddMemberProject() {
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
Toast.show({ type: 'small', text1: 'Terjadi kesalahan', })
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,7 +102,7 @@ export default function AddMemberProject() {
|
||||
headerRight: () => (
|
||||
<ButtonSaveHeader
|
||||
category="update"
|
||||
disable={selectMember.length > 0 ? false : true}
|
||||
disable={selectMember.length == 0 || loading ? true : false}
|
||||
onPress={() => {
|
||||
handleAddMember()
|
||||
}}
|
||||
@@ -150,7 +156,7 @@ export default function AddMemberProject() {
|
||||
</View>
|
||||
</View>
|
||||
{
|
||||
selectMember.some((i: any) => i.idUser == item.id) && <AntDesign name="check" size={20} />
|
||||
selectMember.some((i: any) => i.idUser == item.id) && <AntDesign name="check" size={20} color={'black'}/>
|
||||
}
|
||||
</Pressable>
|
||||
)
|
||||
|
||||
@@ -6,12 +6,16 @@ import Styles from "@/constants/Styles";
|
||||
import { apiCreateProjectTask } from "@/lib/api";
|
||||
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 { useEffect, useState } from "react";
|
||||
import {
|
||||
KeyboardAvoidingView,
|
||||
Platform,
|
||||
Pressable,
|
||||
SafeAreaView,
|
||||
ScrollView,
|
||||
View
|
||||
@@ -23,6 +27,7 @@ import DateTimePicker, {
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
|
||||
export default function ProjectAddTask() {
|
||||
const headerHeight = useHeaderHeight();
|
||||
const { token, decryptToken } = useAuthSession()
|
||||
const dispatch = useDispatch()
|
||||
const update = useSelector((state: any) => state.projectUpdate)
|
||||
@@ -38,6 +43,7 @@ export default function ProjectAddTask() {
|
||||
title: false,
|
||||
})
|
||||
const [title, setTitle] = useState('');
|
||||
const [loading, setLoading] = useState(false)
|
||||
|
||||
const from = range.startDate
|
||||
? dayjs(range.startDate).format("DD-MM-YYYY")
|
||||
@@ -69,15 +75,21 @@ export default function ProjectAddTask() {
|
||||
|
||||
async function handleCreate() {
|
||||
try {
|
||||
setLoading(true)
|
||||
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 });
|
||||
if (response.success) {
|
||||
dispatch(setUpdateProject({ ...update, task: !update.task, progress: !update.progress }))
|
||||
Toast.show({ type: 'small', text1: 'Berhasil menambah 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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,7 +108,7 @@ export default function ProjectAddTask() {
|
||||
headerTitleAlign: "center",
|
||||
headerRight: () => (
|
||||
<ButtonSaveHeader
|
||||
disable={disable}
|
||||
disable={disable || loading}
|
||||
category="create"
|
||||
onPress={() => { handleCreate() }}
|
||||
/>
|
||||
@@ -105,7 +117,7 @@ export default function ProjectAddTask() {
|
||||
/>
|
||||
<KeyboardAvoidingView
|
||||
behavior={Platform.OS === 'ios' ? 'padding' : undefined}
|
||||
keyboardVerticalOffset={110}
|
||||
keyboardVerticalOffset={headerHeight}
|
||||
>
|
||||
<ScrollView>
|
||||
<View style={[Styles.p15, Styles.mb100]}>
|
||||
@@ -119,6 +131,13 @@ export default function ProjectAddTask() {
|
||||
selected: Styles.selectedDate,
|
||||
selected_label: Styles.cWhite,
|
||||
range_fill: Styles.selectRangeDate,
|
||||
month_label: Styles.cBlack,
|
||||
month_selector_label: Styles.cBlack,
|
||||
year_label: Styles.cBlack,
|
||||
year_selector_label: Styles.cBlack,
|
||||
day_label: Styles.cBlack,
|
||||
time_label: Styles.cBlack,
|
||||
weekday_label: Styles.cBlack,
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
@@ -144,6 +163,10 @@ export default function ProjectAddTask() {
|
||||
{
|
||||
(error.endDate || error.startDate) && <Text style={[Styles.textInformation, Styles.cError, Styles.mt05]}>Tanggal tidak boleh kosong</Text>
|
||||
}
|
||||
{/* TODO */}
|
||||
<Pressable style={[Styles.btnTab, Styles.btnLainnya]}>
|
||||
<Text style={[Styles.cWhite]}>Detail</Text>
|
||||
</Pressable>
|
||||
</View>
|
||||
<InputForm
|
||||
label="Judul Tugas"
|
||||
|
||||
@@ -19,6 +19,7 @@ export default function ProjectCancel() {
|
||||
const [reason, setReason] = useState("");
|
||||
const [error, setError] = useState(false);
|
||||
const [disable, setDisable] = useState(false);
|
||||
const [loading, setLoading] = useState(false)
|
||||
|
||||
|
||||
function onValidation(val: string) {
|
||||
@@ -44,6 +45,7 @@ export default function ProjectCancel() {
|
||||
|
||||
async function handleCancel() {
|
||||
try {
|
||||
setLoading(true)
|
||||
const hasil = await decryptToken(String(token?.current));
|
||||
const response = await apiCancelProject({
|
||||
reason: reason,
|
||||
@@ -56,6 +58,9 @@ export default function ProjectCancel() {
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
Toast.show({ type: 'small', text1: 'Terjadi kesalahan', })
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,7 +79,7 @@ export default function ProjectCancel() {
|
||||
headerTitleAlign: "center",
|
||||
headerRight: () => (
|
||||
<ButtonSaveHeader
|
||||
disable={disable}
|
||||
disable={disable || loading}
|
||||
category="cancel"
|
||||
onPress={() => {
|
||||
handleCancel();
|
||||
|
||||
@@ -19,6 +19,7 @@ export default function EditProject() {
|
||||
const [judul, setJudul] = useState("");
|
||||
const [error, setError] = useState(false);
|
||||
const [disable, setDisable] = useState(false);
|
||||
const [loading, setLoading] = useState(false)
|
||||
|
||||
async function handleLoad() {
|
||||
try {
|
||||
@@ -42,6 +43,8 @@ export default function EditProject() {
|
||||
setJudul(val)
|
||||
if (val == "" || val == "null") {
|
||||
setError(true)
|
||||
}else{
|
||||
setError(false)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,6 +62,7 @@ export default function EditProject() {
|
||||
|
||||
async function handleUpdate() {
|
||||
try {
|
||||
setLoading(true)
|
||||
const hasil = await decryptToken(String(token?.current));
|
||||
const response = await apiEditProject({
|
||||
name: judul,
|
||||
@@ -68,9 +72,14 @@ export default function EditProject() {
|
||||
dispatch(setUpdateProject({ ...update, data: !update.data }))
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,7 +100,7 @@ export default function EditProject() {
|
||||
headerTitleAlign: "center",
|
||||
headerRight: () => (
|
||||
<ButtonSaveHeader
|
||||
disable={disable}
|
||||
disable={disable || loading}
|
||||
category="update"
|
||||
onPress={() => { handleUpdate() }}
|
||||
/>
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -12,6 +12,7 @@ import SelectForm from "@/components/selectForm";
|
||||
import Text from "@/components/Text";
|
||||
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,6 +30,7 @@ import Toast from "react-native-toast-message";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
|
||||
export default function CreateProject() {
|
||||
const [loading, setLoading] = useState(false)
|
||||
const { token, decryptToken } = useAuthSession();
|
||||
const [chooseGroup, setChooseGroup] = useState({ val: "", label: "" });
|
||||
const dispatch = useDispatch();
|
||||
@@ -39,7 +41,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)
|
||||
@@ -55,10 +56,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") {
|
||||
@@ -91,6 +103,7 @@ export default function CreateProject() {
|
||||
}
|
||||
|
||||
function handleBack() {
|
||||
dispatch(setGroupChoose(''));
|
||||
dispatch(setTaskCreate([]));
|
||||
dispatch(setMemberChoose([]));
|
||||
router.back();
|
||||
@@ -102,6 +115,7 @@ export default function CreateProject() {
|
||||
|
||||
async function handleCreate() {
|
||||
try {
|
||||
setLoading(true)
|
||||
const hasil = await decryptToken(String(token?.current))
|
||||
const fd = new FormData()
|
||||
|
||||
@@ -127,6 +141,9 @@ export default function CreateProject() {
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
Toast.show({ type: 'small', text1: 'Terjadi kesalahan', })
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -178,7 +195,7 @@ export default function CreateProject() {
|
||||
headerTitleAlign: "center",
|
||||
headerRight: () => (
|
||||
<ButtonSaveHeader
|
||||
disable={disableBtn}
|
||||
disable={disableBtn || loading}
|
||||
category="create"
|
||||
onPress={() => {
|
||||
handleCreate()
|
||||
@@ -234,16 +251,18 @@ export default function CreateProject() {
|
||||
onPress={() => {
|
||||
if (entityUser.role == "supadmin" || entityUser.role == "developer") {
|
||||
if (chooseGroup.val != "") {
|
||||
setSelect(true);
|
||||
setValSelect("member");
|
||||
// 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`);
|
||||
// validationForm('group', userLogin.idGroup, userLogin.group);
|
||||
// setValChoose(userLogin.idGroup)
|
||||
// setSelect(true);
|
||||
// setValSelect("member");
|
||||
}
|
||||
}}
|
||||
error={error.member}
|
||||
|
||||
147
app/(application)/project/create/member.tsx
Normal file
147
app/(application)/project/create/member.tsx
Normal file
@@ -0,0 +1,147 @@
|
||||
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 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={`https://wibu-storage.wibudev.com/api/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={`https://wibu-storage.wibudev.com/api/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>
|
||||
)
|
||||
}
|
||||
@@ -4,8 +4,11 @@ import { InputForm } from "@/components/inputForm";
|
||||
import Text from "@/components/Text";
|
||||
import Styles from "@/constants/Styles";
|
||||
import { setTaskCreate } from "@/lib/taskCreate";
|
||||
import { useHeaderHeight } from '@react-navigation/elements';
|
||||
import dayjs from "dayjs";
|
||||
import { router, Stack } from "expo-router";
|
||||
import 'intl';
|
||||
import 'intl/locale-data/jsonp/id';
|
||||
import { useEffect, useState } from "react";
|
||||
import {
|
||||
KeyboardAvoidingView,
|
||||
@@ -20,6 +23,7 @@ import DateTimePicker, {
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
|
||||
export default function CreateProjectAddTask() {
|
||||
const headerHeight = useHeaderHeight();
|
||||
const dispatch = useDispatch()
|
||||
const [disable, setDisable] = useState(true);
|
||||
const [range, setRange] = useState<{
|
||||
@@ -101,7 +105,7 @@ export default function CreateProjectAddTask() {
|
||||
/>
|
||||
<KeyboardAvoidingView
|
||||
behavior={Platform.OS === 'ios' ? 'padding' : undefined}
|
||||
keyboardVerticalOffset={110}
|
||||
keyboardVerticalOffset={headerHeight}
|
||||
>
|
||||
<ScrollView>
|
||||
<View style={[Styles.p15, Styles.mb100]}>
|
||||
@@ -115,6 +119,13 @@ export default function CreateProjectAddTask() {
|
||||
selected: Styles.selectedDate,
|
||||
selected_label: Styles.cWhite,
|
||||
range_fill: Styles.selectRangeDate,
|
||||
month_label: Styles.cBlack,
|
||||
month_selector_label: Styles.cBlack,
|
||||
year_label: Styles.cBlack,
|
||||
year_selector_label: Styles.cBlack,
|
||||
day_label: Styles.cBlack,
|
||||
time_label: Styles.cBlack,
|
||||
weekday_label: Styles.cBlack,
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
|
||||
@@ -6,8 +6,11 @@ import Styles from "@/constants/Styles";
|
||||
import { apiEditProjectTask, apiGetProjectTask } from "@/lib/api";
|
||||
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 { useEffect, useState } from "react";
|
||||
import { KeyboardAvoidingView, Platform, SafeAreaView, ScrollView, View } from "react-native";
|
||||
import Toast from "react-native-toast-message";
|
||||
@@ -15,6 +18,7 @@ import DateTimePicker, { DateType } from "react-native-ui-datepicker";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
|
||||
export default function UpdateProjectTask() {
|
||||
const headerHeight = useHeaderHeight();
|
||||
const dispatch = useDispatch()
|
||||
const update = useSelector((state: any) => state.projectUpdate)
|
||||
const { token, decryptToken } = useAuthSession();
|
||||
@@ -24,6 +28,7 @@ export default function UpdateProjectTask() {
|
||||
const [year, setYear] = useState<any>()
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [disableBtn, setDisableBtn] = useState(false)
|
||||
const [loadingSubmit, setLoadingSubmit] = useState(false)
|
||||
|
||||
const [title, setTitle] = useState('')
|
||||
const [error, setError] = useState({
|
||||
@@ -65,15 +70,21 @@ export default function UpdateProjectTask() {
|
||||
|
||||
async function handleEdit() {
|
||||
try {
|
||||
setLoadingSubmit(true)
|
||||
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 });
|
||||
if (response.success) {
|
||||
dispatch(setUpdateProject({ ...update, task: !update.task, progress: !update.progress }))
|
||||
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 {
|
||||
setLoadingSubmit(false)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -108,7 +119,7 @@ export default function UpdateProjectTask() {
|
||||
headerTitle: 'Edit Tanggal dan Tugas',
|
||||
headerTitleAlign: 'center',
|
||||
headerRight: () => <ButtonSaveHeader
|
||||
disable={disableBtn}
|
||||
disable={disableBtn || loadingSubmit}
|
||||
category="update"
|
||||
onPress={() => { handleEdit() }}
|
||||
/>
|
||||
@@ -116,7 +127,7 @@ export default function UpdateProjectTask() {
|
||||
/>
|
||||
<KeyboardAvoidingView
|
||||
behavior={Platform.OS === 'ios' ? 'padding' : undefined}
|
||||
keyboardVerticalOffset={110}
|
||||
keyboardVerticalOffset={headerHeight}
|
||||
>
|
||||
<ScrollView>
|
||||
<View style={[Styles.p15, Styles.mb100]}>
|
||||
@@ -129,13 +140,19 @@ export default function UpdateProjectTask() {
|
||||
startDate={range.startDate}
|
||||
endDate={range.endDate}
|
||||
onChange={(param) => setRange(param)}
|
||||
// styles={defaultStyles}
|
||||
month={month}
|
||||
year={year}
|
||||
styles={{
|
||||
selected: Styles.selectedDate,
|
||||
selected_label: Styles.cWhite,
|
||||
range_fill: Styles.selectRangeDate,
|
||||
month_label: Styles.cBlack,
|
||||
month_selector_label: Styles.cBlack,
|
||||
year_label: Styles.cBlack,
|
||||
year_selector_label: Styles.cBlack,
|
||||
day_label: Styles.cBlack,
|
||||
time_label: Styles.cBlack,
|
||||
weekday_label: Styles.cBlack,
|
||||
}}
|
||||
/>
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -51,7 +51,7 @@ export default function FileDivisionDetail() {
|
||||
|
||||
|
||||
const openFile = (item: Props) => {
|
||||
setLoadingOpen(true)
|
||||
if (Platform.OS == 'android') setLoadingOpen(true)
|
||||
let remoteUrl = 'https://wibu-storage.wibudev.com/api/files/' + item.idStorage;
|
||||
const fileName = item.name + '.' + item.extension;
|
||||
let localPath = `${FileSystem.documentDirectory}/${fileName}`;
|
||||
@@ -59,6 +59,7 @@ export default function FileDivisionDetail() {
|
||||
|
||||
FileSystem.downloadAsync(remoteUrl, localPath).then(async ({ uri }) => {
|
||||
const contentURL = await FileSystem.getContentUriAsync(uri);
|
||||
setLoadingOpen(false)
|
||||
try {
|
||||
if (Platform.OS == 'android') {
|
||||
// open with android intent
|
||||
@@ -79,7 +80,7 @@ export default function FileDivisionDetail() {
|
||||
} catch (error) {
|
||||
Alert.alert('INFO', 'Gagal membuka file, tidak ada aplikasi yang dapat membuka file ini');
|
||||
} finally {
|
||||
setLoadingOpen(false)
|
||||
if (Platform.OS == 'android') setLoadingOpen(false)
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -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>
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import { MaterialCommunityIcons, MaterialIcons } from "@expo/vector-icons";
|
||||
import * as DocumentPicker from "expo-document-picker";
|
||||
import { useLocalSearchParams } from "expo-router";
|
||||
import { useState } from "react";
|
||||
import { View } from "react-native";
|
||||
import { ActivityIndicator, View } from "react-native";
|
||||
import Toast from "react-native-toast-message";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import ButtonMenuHeader from "../buttonMenuHeader";
|
||||
@@ -46,26 +46,31 @@ export default function HeaderRightDocument({ path }: { path: string }) {
|
||||
const pickDocumentAsync = async () => {
|
||||
let result = await DocumentPicker.getDocumentAsync({
|
||||
type: ["*/*"],
|
||||
multiple: false,
|
||||
multiple: true,
|
||||
});
|
||||
if (!result.canceled) {
|
||||
if (result.assets?.[0].uri) {
|
||||
handleUploadFile(result.assets?.[0])
|
||||
let file: any[] = []
|
||||
for (let i = 0; i < result.assets?.length; i++) {
|
||||
if (result.assets?.[i].uri) {
|
||||
file.push(result.assets?.[i])
|
||||
}
|
||||
}
|
||||
handleUploadFile(file)
|
||||
}
|
||||
};
|
||||
|
||||
async function handleUploadFile(file: any) {
|
||||
async function handleUploadFile(file: any[]) {
|
||||
try {
|
||||
setLoading(true)
|
||||
const hasil = await decryptToken(String(token?.current))
|
||||
const fd = new FormData()
|
||||
fd.append("file", {
|
||||
uri: file.uri,
|
||||
type: "application/octet-stream",
|
||||
name: file.name,
|
||||
} as any);
|
||||
|
||||
for (let i = 0; i < file.length; i++) {
|
||||
fd.append(`file${i}`, {
|
||||
uri: file[i].uri,
|
||||
type: "application/octet-stream",
|
||||
name: file[i].name,
|
||||
} as any);
|
||||
}
|
||||
fd.append(
|
||||
"data",
|
||||
JSON.stringify({
|
||||
@@ -73,7 +78,7 @@ export default function HeaderRightDocument({ path }: { path: string }) {
|
||||
idDivision: id,
|
||||
user: hasil,
|
||||
})
|
||||
);
|
||||
)
|
||||
|
||||
const response = await apiUploadFileDocument({ data: fd })
|
||||
if (response.success) {
|
||||
@@ -106,28 +111,37 @@ export default function HeaderRightDocument({ path }: { path: string }) {
|
||||
title="Menu"
|
||||
>
|
||||
<View style={Styles.rowItemsCenter}>
|
||||
<MenuItemRow
|
||||
icon={
|
||||
<MaterialCommunityIcons
|
||||
name="folder-open"
|
||||
color="black"
|
||||
size={25}
|
||||
/>
|
||||
}
|
||||
title="Tambah Folder"
|
||||
onPress={() => {
|
||||
setVisible(false);
|
||||
setTimeout(() => {
|
||||
setNewFolder(true);
|
||||
}, 600);
|
||||
}}
|
||||
/>
|
||||
{
|
||||
loading ?
|
||||
<View style={[Styles.contentItemCenter, Styles.w100, Styles.h100]}>
|
||||
<ActivityIndicator size="large" />
|
||||
</View>
|
||||
:
|
||||
<>
|
||||
<MenuItemRow
|
||||
icon={
|
||||
<MaterialCommunityIcons
|
||||
name="folder-open"
|
||||
color="black"
|
||||
size={25}
|
||||
/>
|
||||
}
|
||||
title="Tambah Folder"
|
||||
onPress={() => {
|
||||
setVisible(false);
|
||||
setTimeout(() => {
|
||||
setNewFolder(true);
|
||||
}, 600);
|
||||
}}
|
||||
/>
|
||||
|
||||
<MenuItemRow
|
||||
icon={<MaterialIcons name="upload-file" color="black" size={25} />}
|
||||
title="Upload File"
|
||||
onPress={pickDocumentAsync}
|
||||
/>
|
||||
<MenuItemRow
|
||||
icon={<MaterialIcons name="upload-file" color="black" size={25} />}
|
||||
title="Upload File"
|
||||
onPress={pickDocumentAsync}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
</View>
|
||||
</DrawerBottom>
|
||||
<ModalFloat
|
||||
|
||||
@@ -48,9 +48,9 @@ export default function HeaderRightGroupList() {
|
||||
if (cat === 'title') {
|
||||
setTitle(val)
|
||||
if (val == "" || val.length < 3) {
|
||||
setError({ ...error, title: true })
|
||||
setError((prev) => ({ ...prev, title: true }))
|
||||
} else {
|
||||
setError({ ...error, title: false })
|
||||
setError((prev) => ({ ...prev, title: false }))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -91,7 +91,7 @@ export default function HeaderRightGroupList() {
|
||||
/>
|
||||
</View>
|
||||
<View>
|
||||
<ButtonForm text="SIMPAN" onPress={() => { onCheck() }} />
|
||||
<ButtonForm text="SIMPAN" disabled={Object.values(error).some((v) => v == true) || title == ""} onPress={() => { onCheck() }} />
|
||||
</View>
|
||||
</View>
|
||||
</DrawerBottom>
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { ColorsStatus } from "@/constants/ColorsStatus";
|
||||
import Styles from "@/constants/Styles";
|
||||
import { stringToDate } from "@/lib/fun_stringToDate";
|
||||
import DateTimePicker from "@react-native-community/datetimepicker";
|
||||
import dayjs from "dayjs";
|
||||
import { useState } from "react";
|
||||
import { Pressable, View } from "react-native";
|
||||
import { Platform, Pressable, View } from "react-native";
|
||||
import Text from "./Text";
|
||||
import ModalFloat from "./modalFloat";
|
||||
|
||||
type Props = {
|
||||
label?: string;
|
||||
@@ -58,14 +58,34 @@ export function InputDate({ label, value, placeholder, onChange, info, disable,
|
||||
{info != undefined && (<Text style={[Styles.textInformation, Styles.mt05, Styles.cGray]}>{info}</Text>)}
|
||||
</View>
|
||||
{
|
||||
modal && (
|
||||
<DateTimePicker
|
||||
value={new Date()}
|
||||
mode={mode}
|
||||
display="default"
|
||||
onChange={(event, date) => { onChangeDate(event.type, date) }}
|
||||
onTouchCancel={() => setModal(false)}
|
||||
/>
|
||||
Platform.OS === 'ios' ? (
|
||||
modal && (
|
||||
<ModalFloat
|
||||
isVisible={modal}
|
||||
setVisible={setModal}
|
||||
onSubmit={() => { }}
|
||||
buttonHide
|
||||
disableSubmit
|
||||
title={mode == "date" ? "Pilih Tanggal" : mode == "time" ? "Pilih Jam" : "Pilih Tanggal & Jam"}>
|
||||
<DateTimePicker
|
||||
value={new Date()}
|
||||
mode={mode}
|
||||
display="spinner"
|
||||
onChange={(event, date) => { onChangeDate(event.type, date) }}
|
||||
onTouchCancel={() => setModal(false)}
|
||||
/>
|
||||
</ModalFloat>
|
||||
)
|
||||
) : (
|
||||
modal && (
|
||||
<DateTimePicker
|
||||
value={new Date()}
|
||||
mode={mode}
|
||||
display="inline"
|
||||
onChange={(event, date) => { onChangeDate(event.type, date) }}
|
||||
onTouchCancel={() => setModal(false)}
|
||||
/>
|
||||
)
|
||||
)
|
||||
}
|
||||
</>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import Styles from "@/constants/Styles";
|
||||
import { Dimensions, TextInput, View } from "react-native";
|
||||
import { Dimensions, Platform, TextInput, View } from "react-native";
|
||||
import Text from "./Text";
|
||||
|
||||
type Props = {
|
||||
@@ -36,7 +36,14 @@ export function InputForm({ label, value, placeholder, onChange, info, disable,
|
||||
</Text>
|
||||
)
|
||||
}
|
||||
<View style={[Styles.inputRoundForm, itemRight != undefined ? Styles.inputRoundFormRight : Styles.inputRoundFormLeft, round && Styles.round30, { backgroundColor: bg && bg == 'white' ? 'white' : 'transparent' }, error && { borderColor: "red" }]}>
|
||||
<View style={[
|
||||
Styles.inputRoundForm,
|
||||
itemRight != undefined ? Styles.inputRoundFormRight : Styles.inputRoundFormLeft,
|
||||
round && Styles.round30,
|
||||
{ backgroundColor: bg && bg == 'white' ? 'white' : 'transparent' },
|
||||
error && { borderColor: "red" },
|
||||
Platform.OS == 'ios' && { paddingVertical: 10 },
|
||||
]}>
|
||||
{itemRight != undefined ? itemRight : itemLeft}
|
||||
<TextInput
|
||||
editable={!disable}
|
||||
@@ -71,11 +78,11 @@ export function InputForm({ label, value, placeholder, onChange, info, disable,
|
||||
placeholder={placeholder}
|
||||
keyboardType={type}
|
||||
editable={!disable}
|
||||
style={[Styles.inputRoundForm, error && { borderColor: "red" }, round && Styles.round30, { backgroundColor: bg && bg == 'white' ? 'white' : 'transparent' }, { color: 'black' }, multiline && { height: 100, textAlignVertical: 'top' }]}
|
||||
style={[Styles.inputRoundForm, error && { borderColor: "red" }, round && Styles.round30, { backgroundColor: bg && bg == 'white' ? 'white' : 'transparent' }, { color: 'black' }, multiline && { height: 150, textAlignVertical: 'top' }]}
|
||||
onChangeText={onChange}
|
||||
placeholderTextColor={'gray'}
|
||||
multiline={multiline}
|
||||
numberOfLines={multiline ? 4 : undefined}
|
||||
numberOfLines={multiline ? 5 : undefined}
|
||||
/>
|
||||
{error && (<Text style={[Styles.textInformation, Styles.cError, Styles.mt05]}>{errorText}</Text>)}
|
||||
{info != undefined && (<Text style={[Styles.textInformation, Styles.mt05, Styles.cGray]}>{info}</Text>)}
|
||||
|
||||
@@ -10,7 +10,7 @@ type Props = {
|
||||
}
|
||||
export default function LabelStatus({ category, text, size }: Props) {
|
||||
return (
|
||||
<View style={[size == "small" ? Styles.labelStatusSmall : Styles.labelStatus, ColorsStatus[category], Styles.round10]}>
|
||||
<View style={[size == "small" ? Styles.labelStatusSmall : Styles.labelStatus, ColorsStatus[category], Styles.round10, Styles.contentItemCenter]}>
|
||||
<Text style={[size == "small" ? Styles.textSmallSemiBold : Styles.textMediumSemiBold, Styles.cWhite, { textAlign: 'center' }]}>{text}</Text>
|
||||
</View>
|
||||
)
|
||||
|
||||
@@ -62,7 +62,7 @@ export default function ModalFilter({ open, close, page, category }: Props) {
|
||||
<Pressable key={index} style={[Styles.itemSelectModal]} onPress={() => { setChooseGroup(item.id) }}>
|
||||
<Text style={[chooseGroup == item.id ? Styles.textDefaultSemiBold : Styles.textDefault]}>{item.name}</Text>
|
||||
{
|
||||
chooseGroup == item.id && <AntDesign name="check" size={20} />
|
||||
chooseGroup == item.id && <AntDesign name="check" size={20} color={'black'}/>
|
||||
}
|
||||
</Pressable>
|
||||
))
|
||||
@@ -71,7 +71,7 @@ export default function ModalFilter({ open, close, page, category }: Props) {
|
||||
<Pressable key={index} style={[Styles.itemSelectModal]} onPress={() => { setChooseGroup(item.id) }}>
|
||||
<Text style={[chooseGroup == item.id ? Styles.textDefaultSemiBold : Styles.textDefault]}>{item.name}</Text>
|
||||
{
|
||||
chooseGroup == item.id && <AntDesign name="check" size={20} />
|
||||
chooseGroup == item.id && <AntDesign name="check" size={20} color={'black'}/>
|
||||
}
|
||||
</Pressable>
|
||||
))
|
||||
|
||||
@@ -1,18 +1,19 @@
|
||||
import Styles from '@/constants/Styles';
|
||||
import { Pressable, View } from 'react-native';
|
||||
import Text from './Text';
|
||||
import Modal from 'react-native-modal';
|
||||
import Text from './Text';
|
||||
|
||||
type Props = {
|
||||
isVisible: boolean
|
||||
setVisible: (value: boolean) => void
|
||||
title?: string
|
||||
children: React.ReactNode
|
||||
onSubmit: () => void
|
||||
onSubmit?: () => void
|
||||
disableSubmit?: boolean
|
||||
buttonHide?: boolean
|
||||
}
|
||||
|
||||
export default function ModalFloat({ isVisible, setVisible, title, children, onSubmit, disableSubmit }: Props) {
|
||||
export default function ModalFloat({ isVisible, setVisible, title, children, onSubmit, disableSubmit, buttonHide }: Props) {
|
||||
return (
|
||||
<Modal
|
||||
animationIn={"fadeIn"}
|
||||
@@ -28,14 +29,18 @@ export default function ModalFloat({ isVisible, setVisible, title, children, onS
|
||||
<View style={[Styles.mb10]}>
|
||||
{children}
|
||||
</View>
|
||||
<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>
|
||||
</Pressable>
|
||||
<Pressable style={[Styles.ph15, Styles.pv05, Styles.round10]} onPress={onSubmit} disabled={disableSubmit}>
|
||||
<Text style={[Styles.textDefault, disableSubmit && Styles.cGray]}>Simpan</Text>
|
||||
</Pressable>
|
||||
</View>
|
||||
{
|
||||
!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>
|
||||
</Pressable>
|
||||
<Pressable style={[Styles.ph15, Styles.pv05, Styles.round10]} onPress={onSubmit} disabled={disableSubmit}>
|
||||
<Text style={[Styles.textDefault, disableSubmit && Styles.cGray]}>Simpan</Text>
|
||||
</Pressable>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
</View>
|
||||
</Modal>
|
||||
)
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -9,13 +9,13 @@ import { useAuthSession } from "@/providers/AuthProvider"
|
||||
import { AntDesign } from "@expo/vector-icons"
|
||||
import { useEffect, useState } from "react"
|
||||
import { Pressable, ScrollView, View } from "react-native"
|
||||
import Text from "./Text";
|
||||
import { useDispatch, useSelector } from "react-redux"
|
||||
import { ButtonForm } from "./buttonForm"
|
||||
import DrawerBottom from "./drawerBottom"
|
||||
import ImageUser from "./imageNew"
|
||||
import ImageWithLabel from "./imageWithLabel"
|
||||
import InputSearch from "./inputSearch"
|
||||
import Text from "./Text"
|
||||
|
||||
type Props = {
|
||||
open: boolean
|
||||
@@ -89,8 +89,7 @@ export default function ModalSelect({ open, close, title, category, idParent, on
|
||||
|
||||
useEffect(() => {
|
||||
if (category == 'group') {
|
||||
if (entitiesGroup.length == 0)
|
||||
handleLoadGroup()
|
||||
handleLoadGroup()
|
||||
setData(entitiesGroup)
|
||||
} else if (category == 'position') {
|
||||
handleLoadPosition()
|
||||
@@ -174,9 +173,9 @@ export default function ModalSelect({ open, close, title, category, idParent, on
|
||||
{
|
||||
(category == 'member')
|
||||
?
|
||||
selectMember.some((i: any) => i.idUser == item.id) && <AntDesign name="check" size={20} />
|
||||
selectMember.some((i: any) => i.idUser == item.id) && <AntDesign name="check" size={20} color={'black'} />
|
||||
:
|
||||
chooseValue.val == item.id && <AntDesign name="check" size={20} />
|
||||
chooseValue.val == item.id && <AntDesign name="check" size={20} color={'black'} />
|
||||
}
|
||||
</Pressable>
|
||||
))
|
||||
@@ -192,7 +191,7 @@ export default function ModalSelect({ open, close, title, category, idParent, on
|
||||
}}>
|
||||
<Text style={[chooseValue.val == item.val ? Styles.textDefaultSemiBold : Styles.textDefault]}>{item.label}</Text>
|
||||
{
|
||||
valChoose == item.val && <AntDesign name="check" size={20} />
|
||||
valChoose == item.val && <AntDesign name="check" size={20} color={'black'} />
|
||||
}
|
||||
</Pressable>
|
||||
))
|
||||
|
||||
@@ -161,7 +161,7 @@ export default function ModalSelectMultiple({ open, close, title, category, choo
|
||||
<Text numberOfLines={1} style={[Styles.w80]}>{item.name}</Text>
|
||||
{
|
||||
selectedDivision.some((i: any) => i.id == item.id)
|
||||
? <AntDesign name="check" size={18} />
|
||||
? <AntDesign name="check" size={18} color={'black'}/>
|
||||
: <></>
|
||||
}
|
||||
</Pressable>
|
||||
@@ -177,9 +177,9 @@ export default function ModalSelectMultiple({ open, close, title, category, choo
|
||||
<Text style={[Styles.textMediumSemiBold]}>{item.name}</Text>
|
||||
{
|
||||
checked[item.id] && checked[item.id]?.length === item.Division?.length
|
||||
? <AntDesign name="check" size={20} />
|
||||
? <AntDesign name="check" size={20} color={'black'}/>
|
||||
: (checked[item.id] && checked[item.id]?.length > 0 && checked[item.id]?.length < item.Division?.length)
|
||||
? <AntDesign name="minus" size={20} />
|
||||
? <AntDesign name="minus" size={20} color={'black'}/>
|
||||
: <></>
|
||||
}
|
||||
</Pressable>
|
||||
@@ -189,7 +189,7 @@ export default function ModalSelectMultiple({ open, close, title, category, choo
|
||||
<Pressable key={v} style={[Styles.itemSelectModal]} onPress={() => { handleCheck(item.id, child.id) }}>
|
||||
<Text style={[Styles.ml10, Styles.textMediumNormal, Styles.w80]} numberOfLines={1} ellipsizeMode="tail" >{child.name}</Text>
|
||||
{
|
||||
checked[item.id] && checked[item.id].includes(child.id) && <AntDesign name="check" size={20} />
|
||||
checked[item.id] && checked[item.id].includes(child.id) && <AntDesign name="check" size={20} color={'black'}/>
|
||||
}
|
||||
</Pressable>
|
||||
)
|
||||
|
||||
@@ -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>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -30,19 +30,16 @@ export default function HeaderRightProjectList() {
|
||||
}}
|
||||
/>
|
||||
}
|
||||
{
|
||||
(entityUser.role == 'supadmin' || entityUser.role == 'developer') &&
|
||||
<MenuItemRow
|
||||
icon={<AntDesign name="filter" color="black" size={25} />}
|
||||
title="Filter"
|
||||
onPress={() => {
|
||||
setVisible(false)
|
||||
setTimeout(() => {
|
||||
setFilter(true)
|
||||
}, 600)
|
||||
}}
|
||||
/>
|
||||
}
|
||||
<MenuItemRow
|
||||
icon={<AntDesign name="filter" color="black" size={25} />}
|
||||
title="Filter"
|
||||
onPress={() => {
|
||||
setVisible(false)
|
||||
setTimeout(() => {
|
||||
setFilter(true)
|
||||
}, 600)
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
</DrawerBottom>
|
||||
<ModalFilter
|
||||
|
||||
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([])
|
||||
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>
|
||||
)
|
||||
}
|
||||
@@ -90,12 +90,6 @@ export default function SectionFile({ status, member, refreshing }: { status: nu
|
||||
}
|
||||
}
|
||||
|
||||
// async function download() {
|
||||
// const destination = new Directory(Paths.document, 'pdfs');
|
||||
// const filename = "dummy.pdf";
|
||||
// const result = await File.downloadFileAsync('https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf', destination);
|
||||
// }
|
||||
|
||||
|
||||
const openFile = () => {
|
||||
setModal(false)
|
||||
@@ -173,7 +167,7 @@ export default function SectionFile({ status, member, refreshing }: { status: nu
|
||||
<View style={Styles.rowItemsCenter}>
|
||||
<MenuItemRow
|
||||
icon={<MaterialCommunityIcons name="file-eye" color="black" size={25} />}
|
||||
title="Lihat File"
|
||||
title="Lihat / Share"
|
||||
onPress={() => {
|
||||
openFile()
|
||||
}}
|
||||
|
||||
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>
|
||||
</>
|
||||
}
|
||||
</>
|
||||
)
|
||||
}
|
||||
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}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ export default function SectionCancel({ text, title }: Props) {
|
||||
return (
|
||||
<View style={[ColorsStatus.lightRed, Styles.p10, Styles.round10, Styles.mb15]}>
|
||||
<View style={[Styles.rowItemsCenter]}>
|
||||
<AntDesign name="warning" size={22} style={[Styles.mr10]} />
|
||||
<AntDesign name="warning" size={22} style={[Styles.mr10]} color={'black'}/>
|
||||
<Text style={[Styles.textDefaultSemiBold]}>{title ? title : 'Kegiatan Dibatalkan'}</Text>
|
||||
</View>
|
||||
{
|
||||
|
||||
@@ -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>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -157,7 +157,7 @@ export default function SectionFileTask({refreshing}: {refreshing: boolean}) {
|
||||
<View style={Styles.rowItemsCenter}>
|
||||
<MenuItemRow
|
||||
icon={<MaterialCommunityIcons name="file-eye" color="black" size={25} />}
|
||||
title="Lihat File"
|
||||
title="Lihat / Share"
|
||||
onPress={() => {
|
||||
openFile()
|
||||
// setModal(false)
|
||||
|
||||
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>
|
||||
</>
|
||||
}
|
||||
</>
|
||||
)
|
||||
}
|
||||
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>
|
||||
}
|
||||
</>
|
||||
)
|
||||
}
|
||||
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';
|
||||
|
||||
|
||||
5
constants/ConstEnv.ts
Normal file
5
constants/ConstEnv.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import Constants from 'expo-constants';
|
||||
|
||||
export const ConstEnv = {
|
||||
url_storage : Constants?.expoConfig?.extra?.URL_STORAGE
|
||||
}
|
||||
@@ -45,8 +45,7 @@ const Styles = StyleSheet.create({
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
textLink: {
|
||||
lineHeight: 30,
|
||||
fontSize: 16,
|
||||
fontSize: 14,
|
||||
color: '#0a7ea4',
|
||||
},
|
||||
textInformation: {
|
||||
@@ -270,6 +269,12 @@ const Styles = StyleSheet.create({
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'center'
|
||||
},
|
||||
btnLainnya: {
|
||||
alignSelf: 'flex-start',
|
||||
backgroundColor: '#19345E',
|
||||
paddingVertical: 5,
|
||||
marginVertical: 5
|
||||
},
|
||||
btnMenuRow: {
|
||||
width: '33%',
|
||||
alignItems: 'center'
|
||||
@@ -585,6 +590,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 = (
|
||||
|
||||
46
lib/api.ts
46
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: '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,6 +276,11 @@ export const apiEditProject = async (data: { name: string, user: string }, id: s
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const apiReportProject = async (data: { report: string, user: string }, id: string) => {
|
||||
const response = await api.put(`/mobile/project/${id}/lainnya`, data)
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const apiCreateProjectTask = async ({ data, id }: { data: { name: string, dateStart: string, user: string, dateEnd: string }, id: string }) => {
|
||||
const response = await api.post(`/mobile/project/${id}`, data)
|
||||
return response.data;
|
||||
@@ -294,8 +301,8 @@ export const apiDeleteProjectTask = async (data: { user: string, idProject: stri
|
||||
return response.data
|
||||
};
|
||||
|
||||
export const apiGetProjectTask = async ({ user, id }: { user: string, id: string }) => {
|
||||
const response = await api.get(`mobile/project/detail/${id}?user=${user}`);
|
||||
export const apiGetProjectTask = async ({ user, id, cat }: { user: string, id: string, cat?: string }) => {
|
||||
const response = await api.get(`mobile/project/detail/${id}?user=${user}${cat ? `&cat=${cat}` : ""}`);
|
||||
return response.data;
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
@@ -532,6 +549,11 @@ export const apiDeleteFileTask = async (data: { user: string }, id: string) => {
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const apiDeleteLinkTask = async (data: { user: string, idLink: string }, id: string) => {
|
||||
const response = await api.delete(`/mobile/task/${id}/link`, { data })
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const apiDeleteTaskMember = async (data: { user: string, idUser: string }, id: string) => {
|
||||
const response = await api.delete(`mobile/task/${id}/member`, { data })
|
||||
return response.data
|
||||
@@ -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;
|
||||
|
||||
9
lib/firebaseDatabase.ts
Normal file
9
lib/firebaseDatabase.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { getApp } from '@react-native-firebase/app';
|
||||
import { getDatabase } from '@react-native-firebase/database';
|
||||
|
||||
// Ganti URL sesuai punya kamu
|
||||
const DATABASE_URL = 'https://mobile-darmasaba-default-rtdb.asia-southeast1.firebasedatabase.app';
|
||||
|
||||
export function getDB() {
|
||||
return getDatabase(getApp(), DATABASE_URL);
|
||||
}
|
||||
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) => {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user