Compare commits
97 Commits
amalia/27-
...
amalia/13-
| Author | SHA1 | Date | |
|---|---|---|---|
| 6ffda375a4 | |||
| 2347b322cf | |||
| 13a1d0e858 | |||
| f6ea4f65bb | |||
| 6069756d6f | |||
| 5de8962a0a | |||
| 67ac6d920c | |||
| f9c8c92d3b | |||
| 9b18322f38 | |||
| af24a8af23 | |||
| 95121d0442 | |||
| 5e1ed12ca8 | |||
| 9dde198d5e | |||
| 5fcabc5d77 | |||
| 9fa19af68b | |||
| d2cb7d7738 | |||
| a27c6181dd | |||
| c4e48726e0 | |||
| ab4813d3aa | |||
| 60278fee16 | |||
| 10d4c94cc1 | |||
| 78e7323eab | |||
| ea1c0bd67e | |||
| 1698cc703c | |||
| 43362da45a | |||
| 34d727f07d | |||
| ed175d63f2 | |||
| bd82b7c427 | |||
| a6c96105d2 | |||
| 14e9bf15c7 | |||
| 907b56feaf | |||
| 2341a46992 | |||
| 43a91c6481 | |||
| ecc41c905f | |||
| 65d53951c3 | |||
| c2597b25bf | |||
| f1b3eecbbe | |||
| f042e32d98 | |||
| 93c492ac71 | |||
| 6cca0a3d08 | |||
| bbb25a30d2 | |||
| 46e269b45f | |||
| 4725d27f74 | |||
| ae74791a1c | |||
| ba453ad027 | |||
| 187e9dd19e | |||
| 040cab4f5e | |||
| 7442d01551 | |||
| 180fbeede9 | |||
| d0d40cb1a7 | |||
| 8a25c2f672 | |||
| d31c3677c9 | |||
| 19b02ffc01 | |||
| b9b615636b | |||
| d3e7ef9623 | |||
| d52453c530 | |||
| 1853cb573c | |||
| 83291676d3 | |||
| 1509d1b702 | |||
| 8d6a0d3981 | |||
| d20307fc0b | |||
| 060f96e7b2 | |||
| a15724756e | |||
| c8de5d185a | |||
| 5fa364be24 | |||
| 4768007df3 | |||
| 89bf659598 | |||
| 9793794ff2 | |||
| c435eb1503 | |||
| d0aaa5561c | |||
| 90042d13dd | |||
| 596565ba8e | |||
| 22663acaae | |||
| d3354e3e74 | |||
| c3ab4d05ae | |||
| 75c95b5c92 | |||
| 270001aa4f | |||
| 1122e51047 | |||
| 265656413d | |||
| db0f0ecd6c | |||
| bc590b8cb5 | |||
| 24e1ace521 | |||
| 019c0a5e33 | |||
| 4250ca3057 | |||
| c775b06dc3 | |||
| 77cd07ad7a | |||
| c35e2e65bd | |||
| 6a24b95cdd | |||
| 92c58524f6 | |||
| 7d5ec511f5 | |||
| 3de8e628b6 | |||
| e9f1b14bd6 | |||
| 9607774056 | |||
| 07caea8ae5 | |||
| 94c48889c6 | |||
| d0849143f2 | |||
| a7aeb3d3f9 |
@@ -39,5 +39,7 @@ app-example
|
||||
|
||||
x.ts
|
||||
x.sh
|
||||
/android
|
||||
/ios
|
||||
|
||||
.env
|
||||
|
||||
android/
|
||||
3
.gitignore
vendored
@@ -44,4 +44,5 @@ x.ts
|
||||
x.sh
|
||||
|
||||
google-services.json
|
||||
mobile-darmasaba-firebase-adminsdk-fbsvc-f5abb292b5.json
|
||||
service-account.json
|
||||
|
||||
|
||||
4
android/.kotlin/errors/errors-1757572005452.log
Normal file
@@ -0,0 +1,4 @@
|
||||
kotlin version: 2.0.21
|
||||
error message: The daemon has terminated unexpectedly on startup attempt #1 with error code: 0. The daemon process output:
|
||||
1. Kotlin compile daemon is ready
|
||||
|
||||
@@ -92,8 +92,8 @@ android {
|
||||
applicationId 'mobiledarmasaba.app'
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
versionCode 1
|
||||
versionName "1.0.0"
|
||||
versionCode 6
|
||||
versionName "1.0.2"
|
||||
}
|
||||
signingConfigs {
|
||||
debug {
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools">
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
||||
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO"/>
|
||||
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES"/>
|
||||
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO"/>
|
||||
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
|
||||
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
|
||||
<uses-permission android:name="android.permission.VIBRATE"/>
|
||||
|
||||
|
Before Width: | Height: | Size: 115 KiB After Width: | Height: | Size: 7.1 KiB |
|
Before Width: | Height: | Size: 59 KiB After Width: | Height: | Size: 4.6 KiB |
|
Before Width: | Height: | Size: 186 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 360 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 575 KiB After Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 9.4 KiB After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 4.4 KiB After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 2.4 KiB |
|
Before Width: | Height: | Size: 4.9 KiB After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 69 KiB After Width: | Height: | Size: 4.9 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 3.0 KiB |
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 3.1 KiB |
|
Before Width: | Height: | Size: 134 KiB After Width: | Height: | Size: 8.1 KiB |
|
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 4.5 KiB |
|
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 4.4 KiB |
|
Before Width: | Height: | Size: 218 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 56 KiB After Width: | Height: | Size: 6.3 KiB |
@@ -1,5 +1,5 @@
|
||||
<resources>
|
||||
<string name="app_name">mobile-darmasaba</string>
|
||||
<string name="app_name">Desa+</string>
|
||||
<string name="expo_system_ui_user_interface_style" translatable="false">automatic</string>
|
||||
<string name="expo_splash_screen_resize_mode" translatable="false">contain</string>
|
||||
<string name="expo_splash_screen_status_bar_translucent" translatable="false">false</string>
|
||||
|
||||
@@ -31,7 +31,7 @@ extensions.configure(com.facebook.react.ReactSettingsExtension) { ex ->
|
||||
}
|
||||
expoAutolinking.useExpoModules()
|
||||
|
||||
rootProject.name = 'mobile-darmasaba'
|
||||
rootProject.name = 'Desa+'
|
||||
|
||||
expoAutolinking.useExpoVersionCatalog()
|
||||
|
||||
|
||||
@@ -2,30 +2,40 @@ import 'dotenv/config';
|
||||
|
||||
export default {
|
||||
expo: {
|
||||
name: "mobile-darmasaba",
|
||||
name: "Desa+",
|
||||
slug: "mobile-darmasaba",
|
||||
version: "1.0.0",
|
||||
version: "2.0.2", // Versi aplikasi (App Store)
|
||||
jsEngine: "jsc",
|
||||
orientation: "portrait",
|
||||
icon: "./assets/images/icon.png",
|
||||
icon: "./assets/images/logo-icon-small.png",
|
||||
scheme: "myapp",
|
||||
userInterfaceStyle: "automatic",
|
||||
newArchEnabled: false,
|
||||
ios: {
|
||||
supportsTablet: true,
|
||||
bundleIdentifier: "mobiledarmasaba.app",
|
||||
buildNumber: "4",
|
||||
infoPlist: {
|
||||
ITSAppUsesNonExemptEncryption: false
|
||||
ITSAppUsesNonExemptEncryption: false,
|
||||
CFBundleDisplayName: "Desa+"
|
||||
},
|
||||
googleServicesFile: "./ios/mobiledarmasaba/GoogleService-Info.plist"
|
||||
googleServicesFile: process.env.IOS_GOOGLE_SERVICES_FILE
|
||||
},
|
||||
android: {
|
||||
package: "mobiledarmasaba.app",
|
||||
versionCode: 11,
|
||||
adaptiveIcon: {
|
||||
foregroundImage: "./assets/images/splash-icon.png",
|
||||
foregroundImage: "./assets/images/logo-icon-small.png",
|
||||
backgroundColor: "#ffffff"
|
||||
},
|
||||
googleServicesFile: "./google-services.json"
|
||||
googleServicesFile: "./google-services.json",
|
||||
permissions: [
|
||||
"READ_EXTERNAL_STORAGE",
|
||||
"WRITE_EXTERNAL_STORAGE",
|
||||
"READ_MEDIA_IMAGES", // Android 13+
|
||||
"READ_MEDIA_VIDEO", // Android 13+
|
||||
"READ_MEDIA_AUDIO" // Android 13+
|
||||
]
|
||||
},
|
||||
web: {
|
||||
bundler: "metro",
|
||||
@@ -37,7 +47,7 @@ export default {
|
||||
[
|
||||
"expo-splash-screen",
|
||||
{
|
||||
image: "./assets/images/splash-icon.png",
|
||||
image: "./assets/images/logo-icon-small.png",
|
||||
imageWidth: 200,
|
||||
resizeMode: "contain",
|
||||
backgroundColor: "#ffffff"
|
||||
@@ -50,7 +60,7 @@ export default {
|
||||
"@react-native-firebase/app",
|
||||
{
|
||||
ios: {
|
||||
googleServicesFile: "./ios/mobiledarmasaba/GoogleService-Info.plist"
|
||||
googleServicesFile: process.env.IOS_GOOGLE_SERVICES_FILE
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@@ -19,6 +19,7 @@ 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 { Platform } from "react-native";
|
||||
import { Easing, Notifier } from 'react-native-notifier';
|
||||
import { Provider } from "react-redux";
|
||||
|
||||
@@ -148,7 +149,7 @@ export default function RootLayout() {
|
||||
}}
|
||||
/>
|
||||
</Stack>
|
||||
<StatusBar style="light" translucent={false} backgroundColor="black" />
|
||||
<StatusBar style={'light'} translucent={false} backgroundColor="black" />
|
||||
<ToastCustom />
|
||||
</Provider>
|
||||
)
|
||||
|
||||
@@ -8,7 +8,7 @@ import { useAuthSession } from "@/providers/AuthProvider";
|
||||
import { Entypo, MaterialIcons } from "@expo/vector-icons";
|
||||
import { router, Stack, useLocalSearchParams } from "expo-router";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { Dimensions, SafeAreaView, ScrollView, View } from "react-native";
|
||||
import { Dimensions, RefreshControl, SafeAreaView, ScrollView, View } from "react-native";
|
||||
import RenderHTML from 'react-native-render-html';
|
||||
import { useSelector } from "react-redux";
|
||||
|
||||
@@ -28,6 +28,7 @@ export default function DetailAnnouncement() {
|
||||
const contentWidth = Dimensions.get('window').width
|
||||
const [loading, setLoading] = useState(true)
|
||||
const arrSkeleton = Array.from({ length: 2 }, (_, index) => index)
|
||||
const [refreshing, setRefreshing] = useState(false)
|
||||
|
||||
async function handleLoad(loading: boolean) {
|
||||
try {
|
||||
@@ -56,6 +57,13 @@ export default function DetailAnnouncement() {
|
||||
return htmlRegex.test(text);
|
||||
};
|
||||
|
||||
const handleRefresh = async () => {
|
||||
setRefreshing(true)
|
||||
handleLoad(false)
|
||||
await new Promise(resolve => setTimeout(resolve, 2000));
|
||||
setRefreshing(false)
|
||||
};
|
||||
|
||||
return (
|
||||
<SafeAreaView>
|
||||
<Stack.Screen
|
||||
@@ -66,7 +74,16 @@ export default function DetailAnnouncement() {
|
||||
headerRight: () => entityUser.role != 'user' && entityUser.role != 'coadmin' ? <HeaderRightAnnouncementDetail id={id} /> : <></>,
|
||||
}}
|
||||
/>
|
||||
<ScrollView>
|
||||
<ScrollView
|
||||
showsVerticalScrollIndicator={false}
|
||||
style={[Styles.h100]}
|
||||
refreshControl={
|
||||
<RefreshControl
|
||||
refreshing={refreshing}
|
||||
onRefresh={() => handleRefresh()}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<View style={[Styles.p15]}>
|
||||
<View style={[Styles.wrapPaper]}>
|
||||
{
|
||||
|
||||
@@ -99,7 +99,7 @@ export default function CreateAnnouncement() {
|
||||
headerTitleAlign: "center",
|
||||
headerRight: () => (
|
||||
<ButtonSaveHeader
|
||||
disable={disableBtn || loading ? true : false}
|
||||
disable={disableBtn || divisionMember.length == 0 || loading ? true : false}
|
||||
category="create"
|
||||
onPress={() => {
|
||||
divisionMember.length == 0
|
||||
@@ -110,8 +110,11 @@ export default function CreateAnnouncement() {
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<ScrollView>
|
||||
<View style={[Styles.p15, Styles.mb100]}>
|
||||
<ScrollView
|
||||
showsVerticalScrollIndicator={false}
|
||||
style={[Styles.h100]}
|
||||
>
|
||||
<View style={[Styles.p15]}>
|
||||
<InputForm
|
||||
label="Judul"
|
||||
type="default"
|
||||
|
||||
@@ -153,8 +153,11 @@ export default function EditAnnouncement() {
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<ScrollView>
|
||||
<View style={[Styles.p15, Styles.mb100]}>
|
||||
<ScrollView
|
||||
showsVerticalScrollIndicator={false}
|
||||
style={[Styles.h100]}
|
||||
>
|
||||
<View style={[Styles.p15]}>
|
||||
<InputForm
|
||||
label="Judul"
|
||||
type="default"
|
||||
|
||||
@@ -114,7 +114,7 @@ export default function Announcement() {
|
||||
</View>
|
||||
}
|
||||
title={item.title}
|
||||
desc={item.desc.replace(/<[^>]*>?/gm, '')}
|
||||
desc={item.desc.replace(/<[^>]*>?/gm, '').replace(/\r?\n|\r/g, ' ')}
|
||||
rightTopInfo={item.createdAt}
|
||||
/>
|
||||
)
|
||||
|
||||
@@ -130,7 +130,7 @@ export default function EditBanner() {
|
||||
category="update" />,
|
||||
}}
|
||||
/>
|
||||
<ScrollView>
|
||||
<ScrollView showsVerticalScrollIndicator={false} style={[Styles.h100]}>
|
||||
<View style={[Styles.p15, Styles.mb100]}>
|
||||
<View style={[Styles.mb15]}>
|
||||
{selectedImage != undefined ? (
|
||||
|
||||
@@ -117,8 +117,8 @@ export default function CreateBanner() {
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<ScrollView>
|
||||
<View style={[Styles.p15, Styles.mb100]}>
|
||||
<ScrollView showsVerticalScrollIndicator={false} style={[Styles.h100]}>
|
||||
<View style={[Styles.p15]}>
|
||||
<View style={[Styles.mb15]}>
|
||||
{selectedImage != undefined ? (
|
||||
<Pressable onPress={pickImageAsync}>
|
||||
|
||||
@@ -1,24 +1,29 @@
|
||||
import AlertKonfirmasi from "@/components/alertKonfirmasi";
|
||||
import BorderBottomItem from "@/components/borderBottomItem";
|
||||
import ButtonBackHeader from "@/components/buttonBackHeader";
|
||||
import HeaderRightDiscussionGeneralDetail from "@/components/discussion_general/headerDiscussionDetail";
|
||||
import DrawerBottom from "@/components/drawerBottom";
|
||||
import ImageUser from "@/components/imageNew";
|
||||
import { InputForm } from "@/components/inputForm";
|
||||
import LabelStatus from "@/components/labelStatus";
|
||||
import MenuItemRow from "@/components/menuItemRow";
|
||||
import Skeleton from "@/components/skeleton";
|
||||
import SkeletonContent from "@/components/skeletonContent";
|
||||
import Text from '@/components/Text';
|
||||
import { ColorsStatus } from "@/constants/ColorsStatus";
|
||||
import { ConstEnv } from "@/constants/ConstEnv";
|
||||
import { regexOnlySpacesOrEnter } from "@/constants/OnlySpaceOrEnter";
|
||||
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 { Ionicons, MaterialCommunityIcons, MaterialIcons } from "@expo/vector-icons";
|
||||
import { ref } from '@react-native-firebase/database';
|
||||
import { useHeaderHeight } from '@react-navigation/elements';
|
||||
import { router, Stack, useLocalSearchParams } from "expo-router";
|
||||
import { useEffect, useState } from "react";
|
||||
import { KeyboardAvoidingView, Platform, Pressable, ScrollView, View } from "react-native";
|
||||
import Toast from "react-native-toast-message";
|
||||
import { useSelector } from "react-redux";
|
||||
|
||||
type Props = {
|
||||
@@ -37,6 +42,8 @@ type PropsKomentar = {
|
||||
idUser: string
|
||||
img: string
|
||||
username: string
|
||||
isEdited: boolean
|
||||
updatedAt: string
|
||||
}
|
||||
|
||||
export default function DetailDiscussionGeneral() {
|
||||
@@ -53,6 +60,14 @@ export default function DetailDiscussionGeneral() {
|
||||
const arrSkeleton = Array.from({ length: 3 }, (_, index) => index)
|
||||
const reference = ref(getDB(), `/discussion-general/${id}`);
|
||||
const headerHeight = useHeaderHeight();
|
||||
const [detailMore, setDetailMore] = useState<any>([])
|
||||
const [loadingSendKomentar, setLoadingSendKomentar] = useState(false)
|
||||
const [isVisible, setVisible] = useState(false)
|
||||
const [selectKomentar, setSelectKomentar] = useState({
|
||||
id: '',
|
||||
comment: ''
|
||||
})
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
const onValueChange = reference.on('value', snapshot => {
|
||||
@@ -115,19 +130,29 @@ export default function DetailDiscussionGeneral() {
|
||||
|
||||
async function handleKomentar() {
|
||||
try {
|
||||
setLoadingSendKomentar(true)
|
||||
if (komentar != '') {
|
||||
const hasil = await decryptToken(String(token?.current))
|
||||
const response = await apiSendDiscussionGeneralCommentar({ id: id, data: { desc: komentar, user: hasil } })
|
||||
if (response.success) {
|
||||
setKomentar('')
|
||||
updateTrigger()
|
||||
} else {
|
||||
Toast.show({ type: 'small', text1: 'Gagal menambahkan komentar' })
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
} finally {
|
||||
setLoadingSendKomentar(false)
|
||||
}
|
||||
}
|
||||
|
||||
function handleMenuKomentar(id: string, comment: string) {
|
||||
setSelectKomentar({ id, comment })
|
||||
setVisible(true)
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Stack.Screen
|
||||
@@ -139,7 +164,7 @@ export default function DetailDiscussionGeneral() {
|
||||
}}
|
||||
/>
|
||||
<View style={{ flex: 1 }}>
|
||||
<ScrollView>
|
||||
<ScrollView showsVerticalScrollIndicator={false}>
|
||||
<View style={[Styles.p15, Styles.mb100]}>
|
||||
{
|
||||
loading ?
|
||||
@@ -153,14 +178,13 @@ export default function DetailDiscussionGeneral() {
|
||||
<MaterialIcons name="chat" size={25} color={'#384288'} />
|
||||
</View>
|
||||
}
|
||||
title={data?.title}
|
||||
title={data?.title + " " + data?.createdAt}
|
||||
subtitle={
|
||||
!data?.isActive ?
|
||||
<LabelStatus category='warning' text='ARSIP' size="small" />
|
||||
:
|
||||
<LabelStatus category={data.status == 1 ? 'success' : 'error'} text={data.status == 1 ? 'BUKA' : 'TUTUP'} size="small" />
|
||||
}
|
||||
rightTopInfo={data?.createdAt}
|
||||
desc={data?.desc}
|
||||
leftBottomInfo={
|
||||
<View style={[Styles.rowItemsCenter]}>
|
||||
@@ -168,6 +192,11 @@ export default function DetailDiscussionGeneral() {
|
||||
<Text style={[Styles.textInformation, Styles.cGray, Styles.mb05]}>{dataKomentar.length} Komentar</Text>
|
||||
</View>
|
||||
}
|
||||
rightBottomInfo={
|
||||
<View style={[Styles.rowItemsCenter]}>
|
||||
<Text style={[Styles.textInformation, Styles.cGray, Styles.mb05]}>{data?.createdAt}</Text>
|
||||
</View>
|
||||
}
|
||||
/>
|
||||
}
|
||||
<View style={[Styles.p15]}>
|
||||
@@ -190,6 +219,20 @@ export default function DetailDiscussionGeneral() {
|
||||
title={item.username}
|
||||
rightTopInfo={item.createdAt}
|
||||
desc={item.comment}
|
||||
rightBottomInfo={item.isEdited ? "Edited" : ""}
|
||||
descEllipsize={detailMore.includes(item.id) ? false : true}
|
||||
onPress={() => {
|
||||
setDetailMore((prev: any) => {
|
||||
if (prev.includes(item.id)) {
|
||||
return prev.filter((id: string) => id !== item.id)
|
||||
} else {
|
||||
return [...prev, item.id]
|
||||
}
|
||||
})
|
||||
}}
|
||||
onLongPress={() => {
|
||||
handleMenuKomentar(item.id, item.comment)
|
||||
}}
|
||||
/>
|
||||
)
|
||||
})
|
||||
@@ -206,26 +249,67 @@ export default function DetailDiscussionGeneral() {
|
||||
Styles.w100,
|
||||
{ backgroundColor: "#f4f4f4" },
|
||||
]}>
|
||||
<InputForm
|
||||
disable={(data?.status === 2 || !data?.isActive || (!memberDiscussion && (entityUser.role == "user" || entityUser.role == "coadmin")))}
|
||||
type="default"
|
||||
round
|
||||
placeholder="Kirim Komentar"
|
||||
bg="white"
|
||||
onChange={setKomentar}
|
||||
value={komentar}
|
||||
itemRight={
|
||||
<Pressable onPress={() => {
|
||||
(komentar != '' && data?.status === 1 && data?.isActive && (memberDiscussion || (entityUser.role != "user" && entityUser.role != "coadmin")))
|
||||
&& handleKomentar()
|
||||
}}>
|
||||
<MaterialIcons name="send" size={25} style={(komentar == '' || data?.status === 2 || !data?.isActive || (!memberDiscussion && (entityUser.role == "user" || entityUser.role == "coadmin"))) ? Styles.cGray : Styles.cDefault} />
|
||||
</Pressable>
|
||||
}
|
||||
/>
|
||||
{
|
||||
data?.status != 2 && data?.isActive && ((entityUser.role != "user" && entityUser.role != "coadmin") || memberDiscussion)
|
||||
?
|
||||
<InputForm
|
||||
disable={(data?.status === 2 || !data?.isActive || (!memberDiscussion && (entityUser.role == "user" || entityUser.role == "coadmin")))}
|
||||
type="default"
|
||||
round
|
||||
placeholder="Kirim Komentar"
|
||||
bg="white"
|
||||
onChange={setKomentar}
|
||||
value={komentar}
|
||||
multiline
|
||||
itemRight={
|
||||
<Pressable onPress={() => {
|
||||
(!loadingSendKomentar && komentar != '' && !regexOnlySpacesOrEnter.test(komentar) && data?.status === 1 && data?.isActive && (memberDiscussion || (entityUser.role != "user" && entityUser.role != "coadmin")))
|
||||
&& handleKomentar()
|
||||
}}
|
||||
style={[
|
||||
Platform.OS == 'android' && Styles.mb12,
|
||||
]}
|
||||
>
|
||||
<MaterialIcons name="send" size={25} style={(loadingSendKomentar || komentar == '' || regexOnlySpacesOrEnter.test(komentar) || data?.status === 2 || !data?.isActive || (!memberDiscussion && (entityUser.role == "user" || entityUser.role == "coadmin"))) ? Styles.cGray : Styles.cDefault} />
|
||||
</Pressable>
|
||||
}
|
||||
/>
|
||||
:
|
||||
<View style={[Styles.pv20, { alignItems: 'center' }]}>
|
||||
<Text style={[Styles.textInformation, Styles.cGray]}>
|
||||
{
|
||||
data?.status == 2 ? "Diskusi telah ditutup" : data?.isActive == false ? "Diskusi telah diarsipkan" : "Hanya anggota diskusi yang dapat memberikan komentar"
|
||||
}
|
||||
</Text>
|
||||
</View>
|
||||
}
|
||||
|
||||
</View>
|
||||
</KeyboardAvoidingView>
|
||||
</View >
|
||||
|
||||
<DrawerBottom animation="slide" isVisible={isVisible} setVisible={setVisible} title="Komentar">
|
||||
<View style={Styles.rowItemsCenter}>
|
||||
<MenuItemRow
|
||||
icon={<MaterialCommunityIcons name="pencil-outline" color="black" size={25} />}
|
||||
title="Edit"
|
||||
onPress={() => {
|
||||
setVisible(false)
|
||||
}}
|
||||
/>
|
||||
<MenuItemRow
|
||||
icon={<MaterialIcons name="delete" color="black" size={25} />}
|
||||
title="Hapus"
|
||||
onPress={() => {
|
||||
AlertKonfirmasi({
|
||||
title: 'Konfirmasi',
|
||||
desc: 'Apakah anda yakin ingin menghapus komentar?',
|
||||
onPress: () => { setVisible(false) }
|
||||
})
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
</DrawerBottom>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -116,7 +116,7 @@ export default function AddMemberDiscussionDetail() {
|
||||
selectMember.length > 0
|
||||
?
|
||||
<View>
|
||||
<ScrollView horizontal style={[Styles.mb10, Styles.pv10]}>
|
||||
<ScrollView horizontal style={[Styles.mb10, Styles.pv10]} showsHorizontalScrollIndicator={false}>
|
||||
{
|
||||
selectMember.map((item: any, index: any) => (
|
||||
<ImageWithLabel
|
||||
@@ -133,7 +133,9 @@ export default function AddMemberDiscussionDetail() {
|
||||
:
|
||||
<Text style={[Styles.textDefault, Styles.cGray, Styles.pv05, { textAlign: 'center' }]}>Tidak ada member yang dipilih</Text>
|
||||
}
|
||||
<ScrollView>
|
||||
<ScrollView
|
||||
showsVerticalScrollIndicator={false}
|
||||
>
|
||||
|
||||
{
|
||||
data.length > 0 ?
|
||||
|
||||
@@ -142,7 +142,7 @@ export default function CreateDiscussionGeneral() {
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<ScrollView>
|
||||
<ScrollView showsVerticalScrollIndicator={false} style={[Styles.h100]}>
|
||||
<View style={[Styles.p15, Styles.mb100]}>
|
||||
{
|
||||
(entityUser.role == "supadmin" ||
|
||||
|
||||
@@ -119,7 +119,7 @@ export default function EditDiscussionGeneral() {
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<ScrollView>
|
||||
<ScrollView showsVerticalScrollIndicator={false} style={[Styles.h100]}>
|
||||
<View style={[Styles.p15]}>
|
||||
<InputForm
|
||||
label="Judul"
|
||||
|
||||
@@ -98,22 +98,26 @@ export default function Discussion() {
|
||||
return (
|
||||
<View style={[Styles.p15, { flex: 1 }]}>
|
||||
<View>
|
||||
<View style={[Styles.wrapBtnTab]}>
|
||||
<ButtonTab
|
||||
active={status == "false" ? "false" : "true"}
|
||||
value="true"
|
||||
onPress={() => { setStatus("true") }}
|
||||
label="Aktif"
|
||||
icon={<Feather name="check-circle" color={status == "false" ? 'black' : 'white'} size={20} />}
|
||||
n={2} />
|
||||
<ButtonTab
|
||||
active={status == "false" ? "false" : "true"}
|
||||
value="false"
|
||||
onPress={() => { setStatus("false") }}
|
||||
label="Arsip"
|
||||
icon={<AntDesign name="closecircleo" color={status == "true" ? 'black' : 'white'} size={20} />}
|
||||
n={2} />
|
||||
</View>
|
||||
{
|
||||
entityUser.role != "user" && entityUser.role != "coadmin" &&
|
||||
<View style={[Styles.wrapBtnTab]}>
|
||||
<ButtonTab
|
||||
active={status == "false" ? "false" : "true"}
|
||||
value="true"
|
||||
onPress={() => { setStatus("true") }}
|
||||
label="Aktif"
|
||||
icon={<Feather name="check-circle" color={status == "false" ? 'black' : 'white'} size={20} />}
|
||||
n={2} />
|
||||
<ButtonTab
|
||||
active={status == "false" ? "false" : "true"}
|
||||
value="false"
|
||||
onPress={() => { setStatus("false") }}
|
||||
label="Arsip"
|
||||
icon={<AntDesign name="closecircleo" color={status == "true" ? 'black' : 'white'} size={20} />}
|
||||
n={2} />
|
||||
</View>
|
||||
}
|
||||
|
||||
<InputSearch onChange={setSearch} />
|
||||
{
|
||||
(entityUser.role == "supadmin" || entityUser.role == "developer") &&
|
||||
@@ -153,7 +157,7 @@ export default function Discussion() {
|
||||
status != "false" && <LabelStatus category={item.status === 1 ? "success" : "error"} text={item.status === 1 ? "BUKA" : "TUTUP"} size="small" />
|
||||
}
|
||||
rightTopInfo={item.createdAt}
|
||||
desc={item.desc}
|
||||
desc={item.desc.replace(/<[^>]*>?/gm, ' ').replace(/\r?\n|\r/g, ' ')}
|
||||
leftBottomInfo={
|
||||
<View style={[Styles.rowItemsCenter]}>
|
||||
<Ionicons name="chatbox-ellipses-outline" size={18} color="grey" style={Styles.mr05} />
|
||||
@@ -176,34 +180,6 @@ export default function Discussion() {
|
||||
/>
|
||||
}
|
||||
/>
|
||||
// data.map((item: any, i: number) => {
|
||||
// return (
|
||||
// <BorderBottomItem
|
||||
// key={i}
|
||||
// onPress={() => { router.push(`/discussion/${item.id}`) }}
|
||||
// borderType="bottom"
|
||||
// icon={
|
||||
// <View style={[Styles.iconContent, ColorsStatus.lightGreen]}>
|
||||
// <MaterialIcons name="chat" size={25} color={'#384288'} />
|
||||
// </View>
|
||||
// }
|
||||
// title={item.title}
|
||||
// subtitle={
|
||||
// status != "false" && <LabelStatus category={item.status === 1 ? "success" : "error"} text={item.status === 1 ? "BUKA" : "TUTUP"} size="small" />
|
||||
// }
|
||||
// rightTopInfo={item.createdAt}
|
||||
// desc={item.desc}
|
||||
// leftBottomInfo={
|
||||
// <View style={[Styles.rowItemsCenter]}>
|
||||
// <Ionicons name="chatbox-ellipses-outline" size={18} color="grey" style={Styles.mr05} />
|
||||
// <Text style={[Styles.textInformation, Styles.cGray, Styles.mb05]}>Diskusikan</Text>
|
||||
// </View>
|
||||
// }
|
||||
// rightBottomInfo={`${item.total_komentar} Komentar`}
|
||||
|
||||
// />
|
||||
// )
|
||||
// })
|
||||
:
|
||||
<Text style={[Styles.textDefault, Styles.cGray, { textAlign: 'center' }]}>Tidak ada data</Text>
|
||||
}
|
||||
|
||||
@@ -135,22 +135,25 @@ export default function MemberDiscussionDetail() {
|
||||
router.push(`/member/${chooseUser.idUser}`)
|
||||
}}
|
||||
/>
|
||||
{
|
||||
entityUser.role != "user" && entityUser.role != "coadmin" &&
|
||||
<MenuItemRow
|
||||
icon={<MaterialCommunityIcons name="account-remove" color="black" size={25} />}
|
||||
title="Keluarkan"
|
||||
onPress={() => {
|
||||
setModal(false)
|
||||
AlertKonfirmasi({
|
||||
title: 'Konfirmasi',
|
||||
desc: 'Apakah Anda yakin ingin mengeluarkan anggota?',
|
||||
onPress: () => {
|
||||
handleDeleteUser()
|
||||
}
|
||||
})
|
||||
|
||||
<MenuItemRow
|
||||
icon={<MaterialCommunityIcons name="account-remove" color="black" size={25} />}
|
||||
title="Keluarkan"
|
||||
onPress={() => {
|
||||
setModal(false)
|
||||
AlertKonfirmasi({
|
||||
title: 'Konfirmasi',
|
||||
desc: 'Apakah Anda yakin ingin mengeluarkan anggota?',
|
||||
onPress: () => {
|
||||
handleDeleteUser()
|
||||
}
|
||||
})
|
||||
}}
|
||||
/>
|
||||
}
|
||||
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
</DrawerBottom>
|
||||
</SafeAreaView>
|
||||
|
||||
@@ -123,7 +123,7 @@ export default function AddMemberCalendarEvent() {
|
||||
selectMember.length > 0
|
||||
?
|
||||
<View>
|
||||
<ScrollView horizontal style={[Styles.mb10, Styles.pv10]}>
|
||||
<ScrollView horizontal style={[Styles.mb10, Styles.pv10]} showsHorizontalScrollIndicator={false}>
|
||||
{
|
||||
selectMember.map((item: any, index: any) => (
|
||||
<ImageWithLabel
|
||||
@@ -140,7 +140,10 @@ export default function AddMemberCalendarEvent() {
|
||||
:
|
||||
<Text style={[Styles.textDefault, Styles.cGray, Styles.pv05, { textAlign: 'center' }]}>Tidak ada member yang dipilih</Text>
|
||||
}
|
||||
<ScrollView>
|
||||
<ScrollView
|
||||
showsVerticalScrollIndicator={false}
|
||||
style={[Styles.h100]}
|
||||
>
|
||||
{
|
||||
data.length > 0 ?
|
||||
data.map((item: any, index: any) => {
|
||||
@@ -151,7 +154,6 @@ export default function AddMemberCalendarEvent() {
|
||||
style={[Styles.itemSelectModal]}
|
||||
onPress={() => {
|
||||
!found && onChoose(item.idUser, item.name, item.img)
|
||||
onChoose(item.idUser, item.name, item.img)
|
||||
}}
|
||||
>
|
||||
<View style={[Styles.rowItemsCenter]}>
|
||||
@@ -164,7 +166,7 @@ export default function AddMemberCalendarEvent() {
|
||||
</View>
|
||||
</View>
|
||||
{
|
||||
selectMember.some((i: any) => i.idUser == item.id) && <AntDesign name="check" size={20} color={'black'} />
|
||||
selectMember.some((i: any) => i.idUser == item.idUser) && <AntDesign name="check" size={20} color={'black'} />
|
||||
}
|
||||
</Pressable>
|
||||
)
|
||||
|
||||
@@ -13,9 +13,10 @@ import { apiDeleteCalendarMember, apiGetCalendarOne, apiGetDivisionOneFeature }
|
||||
import { setUpdateCalendar } from "@/lib/calendarUpdate"
|
||||
import { useAuthSession } from "@/providers/AuthProvider"
|
||||
import { MaterialCommunityIcons } from "@expo/vector-icons"
|
||||
import Clipboard from "@react-native-clipboard/clipboard"
|
||||
import { router, Stack, useLocalSearchParams } from "expo-router"
|
||||
import { useEffect, useState } from "react"
|
||||
import { RefreshControl, SafeAreaView, ScrollView, View } from "react-native"
|
||||
import { Pressable, RefreshControl, SafeAreaView, ScrollView, View } from "react-native"
|
||||
import Toast from "react-native-toast-message"
|
||||
import { useDispatch, useSelector } from "react-redux"
|
||||
|
||||
@@ -115,6 +116,11 @@ export default function DetailEventCalendar() {
|
||||
handleLoadMember();
|
||||
}, [update.member]);
|
||||
|
||||
const handleCopy = (text: string) => {
|
||||
Clipboard.setString(text);
|
||||
Toast.show({ type: 'small', text1: 'Berhasil menyalin link', })
|
||||
};
|
||||
|
||||
async function handleDeleteUser() {
|
||||
try {
|
||||
const hasil = await decryptToken(String(token?.current));
|
||||
@@ -216,10 +222,14 @@ export default function DetailEventCalendar() {
|
||||
loading ?
|
||||
<Skeleton width={80} height={10} borderRadius={10} widthType="percent" />
|
||||
:
|
||||
<Text style={[Styles.textDefault]}>{data?.linkMeet ? data.linkMeet : '-'}</Text>
|
||||
data?.linkMeet ?
|
||||
<Pressable onPress={() => { handleCopy(data.linkMeet) }}>
|
||||
<Text style={[Styles.textDefault]}>{data.linkMeet}</Text>
|
||||
</Pressable>
|
||||
: <Text style={[Styles.textDefault]}>-</Text>
|
||||
}
|
||||
</View>
|
||||
<View style={[Styles.rowItemsCenter, Styles.mt10, {alignItems:'flex-start'}]}>
|
||||
<View style={[Styles.rowItemsCenter, Styles.mt10, { alignItems: 'flex-start' }]}>
|
||||
<MaterialCommunityIcons name="card-text-outline" size={30} color="black" style={Styles.mr10} />
|
||||
{
|
||||
loading ?
|
||||
|
||||
@@ -112,7 +112,7 @@ export default function CreateCalendarAddMember() {
|
||||
selectMember.length > 0
|
||||
?
|
||||
<View>
|
||||
<ScrollView horizontal style={[Styles.mb10, Styles.pv10]}>
|
||||
<ScrollView horizontal style={[Styles.mb10, Styles.pv10]} showsHorizontalScrollIndicator={false}>
|
||||
{
|
||||
selectMember.map((item: any, index: any) => (
|
||||
<ImageWithLabel
|
||||
@@ -129,7 +129,10 @@ export default function CreateCalendarAddMember() {
|
||||
:
|
||||
<Text style={[Styles.textDefault, Styles.cGray, Styles.pv05, { textAlign: 'center' }]}>Tidak ada member yang dipilih</Text>
|
||||
}
|
||||
<ScrollView>
|
||||
<ScrollView
|
||||
showsVerticalScrollIndicator={false}
|
||||
style={[Styles.h100]}
|
||||
>
|
||||
|
||||
{
|
||||
data.length > 0 ?
|
||||
|
||||
@@ -93,6 +93,7 @@ export default function DiscussionDivisionEdit() {
|
||||
required
|
||||
value={data}
|
||||
onChange={setData}
|
||||
multiline
|
||||
/>
|
||||
</View>
|
||||
</ScrollView>
|
||||
|
||||
@@ -8,6 +8,7 @@ import Skeleton from "@/components/skeleton";
|
||||
import SkeletonContent from "@/components/skeletonContent";
|
||||
import Text from "@/components/Text";
|
||||
import { ConstEnv } from "@/constants/ConstEnv";
|
||||
import { regexOnlySpacesOrEnter } from "@/constants/OnlySpaceOrEnter";
|
||||
import Styles from "@/constants/Styles";
|
||||
import {
|
||||
apiGetDiscussionOne,
|
||||
@@ -63,6 +64,7 @@ export default function DiscussionDetail() {
|
||||
const reference = ref(getDB(), `/discussion-division/${detail}`);
|
||||
const [refreshing, setRefreshing] = useState(false)
|
||||
const headerHeight = useHeaderHeight();
|
||||
const [detailMore, setDetailMore] = useState<any>([])
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
@@ -275,7 +277,16 @@ export default function DiscussionDetail() {
|
||||
title={item.username}
|
||||
rightTopInfo={item.createdAt}
|
||||
desc={item.comment}
|
||||
descEllipsize={false}
|
||||
descEllipsize={detailMore.includes(item.id) ? false : true}
|
||||
onPress={() => {
|
||||
setDetailMore((prev: any) => {
|
||||
if (prev.includes(item.id)) {
|
||||
return prev.filter((id: string) => id !== item.id)
|
||||
} else {
|
||||
return [...prev, item.id]
|
||||
}
|
||||
})
|
||||
}}
|
||||
/>
|
||||
))
|
||||
}
|
||||
@@ -294,54 +305,67 @@ export default function DiscussionDetail() {
|
||||
{ backgroundColor: "#f4f4f4" },
|
||||
]}
|
||||
>
|
||||
<InputForm
|
||||
disable={
|
||||
data?.status == 2 ||
|
||||
data?.isActive == false ||
|
||||
((entityUser.role == "user" || entityUser.role == "coadmin") &&
|
||||
!isMemberDivision)
|
||||
}
|
||||
bg="white"
|
||||
type="default"
|
||||
round
|
||||
placeholder="Kirim Komentar"
|
||||
onChange={setKomentar}
|
||||
value={komentar}
|
||||
itemRight={
|
||||
<Pressable
|
||||
onPress={() => {
|
||||
komentar != "" &&
|
||||
!loadingSend &&
|
||||
data?.status != 2 &&
|
||||
data?.isActive &&
|
||||
(((entityUser.role == "user" ||
|
||||
entityUser.role == "coadmin") &&
|
||||
isMemberDivision) ||
|
||||
entityUser.role == "admin" ||
|
||||
entityUser.role == "supadmin" ||
|
||||
entityUser.role == "developer" ||
|
||||
entityUser.role == "cosupadmin") &&
|
||||
handleKomentar();
|
||||
}}
|
||||
>
|
||||
<MaterialIcons
|
||||
name="send"
|
||||
size={25}
|
||||
style={
|
||||
komentar == "" ||
|
||||
loadingSend ||
|
||||
data?.status == 2 ||
|
||||
data?.isActive == false ||
|
||||
((entityUser.role == "user" ||
|
||||
entityUser.role == "coadmin") &&
|
||||
!isMemberDivision)
|
||||
? Styles.cGray
|
||||
: Styles.cDefault
|
||||
{
|
||||
data?.status != 2 && data?.isActive && ((entityUser.role != "user" && entityUser.role != "coadmin") ||
|
||||
isMemberDivision)
|
||||
?
|
||||
<InputForm
|
||||
// disable={
|
||||
// data?.status == 2 ||
|
||||
// data?.isActive == false ||
|
||||
// ((entityUser.role == "user" || entityUser.role == "coadmin") && !isMemberDivision)
|
||||
// }
|
||||
bg="white"
|
||||
type="default"
|
||||
round
|
||||
multiline
|
||||
placeholder="Kirim Komentar"
|
||||
onChange={setKomentar}
|
||||
value={komentar}
|
||||
itemRight={
|
||||
<Pressable
|
||||
onPress={() => {
|
||||
komentar != "" &&
|
||||
!regexOnlySpacesOrEnter.test(komentar) &&
|
||||
!loadingSend &&
|
||||
data?.status != 2 &&
|
||||
data?.isActive &&
|
||||
(((entityUser.role == "user" ||
|
||||
entityUser.role == "coadmin") &&
|
||||
isMemberDivision) ||
|
||||
entityUser.role == "admin" ||
|
||||
entityUser.role == "supadmin" ||
|
||||
entityUser.role == "developer" ||
|
||||
entityUser.role == "cosupadmin") &&
|
||||
handleKomentar();
|
||||
}}
|
||||
style={[
|
||||
Platform.OS == 'android' && Styles.mb12,
|
||||
]}
|
||||
>
|
||||
<MaterialIcons
|
||||
name="send"
|
||||
size={25}
|
||||
style={
|
||||
[komentar == "" || regexOnlySpacesOrEnter.test(komentar) || loadingSend || ((entityUser.role == "user" || entityUser.role == "coadmin") && !isMemberDivision)
|
||||
? Styles.cGray
|
||||
: Styles.cDefault,
|
||||
]
|
||||
}
|
||||
/>
|
||||
</Pressable>
|
||||
}
|
||||
/>
|
||||
:
|
||||
<View style={[Styles.pv20, { alignItems: 'center' }]}>
|
||||
<Text style={[Styles.textInformation, Styles.cGray]}>
|
||||
{
|
||||
data?.status == 2 ? "Diskusi telah ditutup" : data?.isActive == false ? "Diskusi telah diarsipkan" : "Hanya anggota divisi yang dapat memberikan komentar"
|
||||
}
|
||||
/>
|
||||
</Pressable>
|
||||
}
|
||||
/>
|
||||
</Text>
|
||||
</View>
|
||||
}
|
||||
|
||||
</View>
|
||||
</KeyboardAvoidingView>
|
||||
|
||||
|
||||
@@ -56,7 +56,14 @@ export default function CreateDiscussionDivision() {
|
||||
/>
|
||||
<ScrollView>
|
||||
<View style={[Styles.p15, Styles.mb100]}>
|
||||
<InputForm label="Diskusi" type="default" placeholder="Hal yang didiskusikan" required onChange={setDesc} />
|
||||
<InputForm
|
||||
label="Diskusi"
|
||||
type="default"
|
||||
placeholder="Hal yang didiskusikan"
|
||||
required
|
||||
onChange={setDesc}
|
||||
multiline
|
||||
/>
|
||||
</View>
|
||||
</ScrollView>
|
||||
</SafeAreaView>
|
||||
|
||||
@@ -7,7 +7,7 @@ import SkeletonContent from "@/components/skeletonContent";
|
||||
import Text from "@/components/Text";
|
||||
import { ConstEnv } from "@/constants/ConstEnv";
|
||||
import Styles from "@/constants/Styles";
|
||||
import { apiGetDiscussion } from "@/lib/api";
|
||||
import { apiGetDiscussion, apiGetDivisionOneFeature } from "@/lib/api";
|
||||
import { useAuthSession } from "@/providers/AuthProvider";
|
||||
import { AntDesign, Feather, Ionicons } from "@expo/vector-icons";
|
||||
import { router, useLocalSearchParams } from "expo-router";
|
||||
@@ -41,6 +41,30 @@ export default function DiscussionDivision() {
|
||||
const [waiting, setWaiting] = useState(false)
|
||||
const [status, setStatus] = useState<'true' | 'false'>('true')
|
||||
const [refreshing, setRefreshing] = useState(false)
|
||||
const [isMemberDivision, setIsMemberDivision] = useState(false)
|
||||
const [isAdminDivision, setIsAdminDivision] = useState(false)
|
||||
const entityUser = useSelector((state: any) => state.user)
|
||||
|
||||
async function handleCheckMember() {
|
||||
try {
|
||||
const hasil = await decryptToken(String(token?.current));
|
||||
const response = await apiGetDivisionOneFeature({
|
||||
id,
|
||||
user: hasil,
|
||||
cat: "check-member",
|
||||
});
|
||||
|
||||
const response2 = await apiGetDivisionOneFeature({
|
||||
id,
|
||||
user: hasil,
|
||||
cat: "check-admin",
|
||||
});
|
||||
setIsMemberDivision(response.data);
|
||||
setIsAdminDivision(response2.data);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleLoad(loading: boolean, thisPage: number) {
|
||||
try {
|
||||
@@ -80,6 +104,10 @@ export default function DiscussionDivision() {
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
handleCheckMember()
|
||||
}, [])
|
||||
|
||||
const handleRefresh = async () => {
|
||||
setRefreshing(true)
|
||||
handleLoad(false, 1)
|
||||
@@ -101,25 +129,29 @@ export default function DiscussionDivision() {
|
||||
|
||||
return (
|
||||
<View style={[Styles.p15, { flex: 1 }]}>
|
||||
<View>
|
||||
<View style={[Styles.wrapBtnTab]}>
|
||||
<ButtonTab
|
||||
active={status == "false" ? "false" : "true"}
|
||||
value="true"
|
||||
onPress={() => { setStatus("true") }}
|
||||
label="Aktif"
|
||||
icon={<Feather name="check-circle" color={status == "false" ? 'black' : 'white'} size={20} />}
|
||||
n={2} />
|
||||
<ButtonTab
|
||||
active={status == "false" ? "false" : "true"}
|
||||
value="false"
|
||||
onPress={() => { setStatus("false") }}
|
||||
label="Arsip"
|
||||
icon={<AntDesign name="closecircleo" color={status == "true" ? 'black' : 'white'} size={20} />}
|
||||
n={2} />
|
||||
{
|
||||
((entityUser.role != "user" && entityUser.role != "coadmin") || isAdminDivision) &&
|
||||
<View>
|
||||
<View style={[Styles.wrapBtnTab]}>
|
||||
<ButtonTab
|
||||
active={status == "false" ? "false" : "true"}
|
||||
value="true"
|
||||
onPress={() => { setStatus("true") }}
|
||||
label="Aktif"
|
||||
icon={<Feather name="check-circle" color={status == "false" ? 'black' : 'white'} size={20} />}
|
||||
n={2} />
|
||||
<ButtonTab
|
||||
active={status == "false" ? "false" : "true"}
|
||||
value="false"
|
||||
onPress={() => { setStatus("false") }}
|
||||
label="Arsip"
|
||||
icon={<AntDesign name="closecircleo" color={status == "true" ? 'black' : 'white'} size={20} />}
|
||||
n={2} />
|
||||
</View>
|
||||
<InputSearch onChange={setSearch} />
|
||||
</View>
|
||||
<InputSearch onChange={setSearch} />
|
||||
</View>
|
||||
}
|
||||
|
||||
|
||||
<View style={[{ flex: 2 }, Styles.mt05]}>
|
||||
{
|
||||
|
||||
@@ -18,6 +18,7 @@ import Styles from "@/constants/Styles";
|
||||
import {
|
||||
apiDocumentDelete,
|
||||
apiDocumentRename,
|
||||
apiGetDivisionOneFeature,
|
||||
apiGetDocument,
|
||||
apiShareDocument,
|
||||
} from "@/lib/api";
|
||||
@@ -65,6 +66,7 @@ type PropsPath = {
|
||||
};
|
||||
|
||||
export default function DocumentDivision() {
|
||||
const [loadingRename, setLoadingRename] = useState(false)
|
||||
const [isShare, setShare] = useState(false)
|
||||
const { token, decryptToken } = useAuthSession()
|
||||
const { id } = useLocalSearchParams<{ id: string }>()
|
||||
@@ -84,6 +86,8 @@ export default function DocumentDivision() {
|
||||
const update = useSelector((state: any) => state.dokumenUpdate)
|
||||
const [refreshing, setRefreshing] = useState(false)
|
||||
const [loadingOpen, setLoadingOpen] = useState(false)
|
||||
const [isMemberDivision, setIsMemberDivision] = useState(false)
|
||||
const entityUser = useSelector((state: any) => state.user)
|
||||
const [bodyRename, setBodyRename] = useState({
|
||||
id: "",
|
||||
name: "",
|
||||
@@ -92,6 +96,24 @@ export default function DocumentDivision() {
|
||||
extension: "",
|
||||
});
|
||||
|
||||
async function handleCheckMember() {
|
||||
try {
|
||||
const hasil = await decryptToken(String(token?.current));
|
||||
const response = await apiGetDivisionOneFeature({
|
||||
id,
|
||||
user: hasil,
|
||||
cat: "check-member",
|
||||
});
|
||||
setIsMemberDivision(response.data);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
handleCheckMember()
|
||||
}, [id])
|
||||
|
||||
async function handleLoad(loading: boolean) {
|
||||
try {
|
||||
setLoading(loading)
|
||||
@@ -201,6 +223,7 @@ export default function DocumentDivision() {
|
||||
|
||||
async function handleRename() {
|
||||
try {
|
||||
setLoadingRename(true)
|
||||
const hasil = await decryptToken(String(token?.current));
|
||||
const response = await apiDocumentRename({ user: hasil, ...bodyRename });
|
||||
if (response.success) {
|
||||
@@ -214,7 +237,8 @@ export default function DocumentDivision() {
|
||||
console.error(error);
|
||||
Toast.show({ type: 'small', text1: 'Terjadi kesalahan', })
|
||||
} finally {
|
||||
setRename(false);
|
||||
setLoadingRename(false)
|
||||
setRename(false)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -344,7 +368,7 @@ export default function DocumentDivision() {
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<HeaderRightDocument path={path} />
|
||||
<HeaderRightDocument path={path} isMember={isMemberDivision} />
|
||||
),
|
||||
}}
|
||||
/>
|
||||
@@ -360,25 +384,20 @@ export default function DocumentDivision() {
|
||||
<View style={[Styles.p15, Styles.mb100]}>
|
||||
<View style={[Styles.rowItemsCenter]}>
|
||||
{
|
||||
loading ?
|
||||
arrSkeleton.map((item, index) => (
|
||||
<Skeleton key={index} width={60} height={10} borderRadius={10} style={[Styles.mr05]} />
|
||||
))
|
||||
:
|
||||
dataJalur.map((item, index) => (
|
||||
<Pressable
|
||||
key={index}
|
||||
style={[Styles.rowItemsCenter]}
|
||||
onPress={() => {
|
||||
setPath(item.id);
|
||||
}}
|
||||
>
|
||||
{item.id != "home" && (
|
||||
<AntDesign name="right" style={[Styles.mh05, Styles.mt02]} color="black" />
|
||||
)}
|
||||
<Text> {item.name} </Text>
|
||||
</Pressable>
|
||||
))
|
||||
dataJalur.map((item, index) => (
|
||||
<Pressable
|
||||
key={index}
|
||||
style={[Styles.rowItemsCenter]}
|
||||
onPress={() => {
|
||||
setPath(item.id);
|
||||
}}
|
||||
>
|
||||
{item.id != "home" && (
|
||||
<AntDesign name="right" style={[Styles.mh05, Styles.mt02]} color="black" />
|
||||
)}
|
||||
<Text> {item.name} </Text>
|
||||
</Pressable>
|
||||
))
|
||||
}
|
||||
</View>
|
||||
<View>
|
||||
@@ -409,6 +428,7 @@ export default function DocumentDivision() {
|
||||
: `${item.name}.${item.extension}`
|
||||
}
|
||||
dateTime={item.createdAt}
|
||||
canChecked={(entityUser.role != "user" && entityUser.role != "coadmin") || isMemberDivision}
|
||||
onChecked={() => {
|
||||
handleCheckboxChange(index);
|
||||
}}
|
||||
@@ -538,7 +558,7 @@ export default function DocumentDivision() {
|
||||
isVisible={isRename}
|
||||
setVisible={() => { setRename(false) }}
|
||||
onSubmit={() => { handleRename() }}
|
||||
disableSubmit={bodyRename.name == ""}
|
||||
disableSubmit={bodyRename.name == "" || loadingRename}
|
||||
>
|
||||
<View>
|
||||
<InputForm
|
||||
|
||||
@@ -12,7 +12,7 @@ import { useAuthSession } from "@/providers/AuthProvider";
|
||||
import { AntDesign } from "@expo/vector-icons";
|
||||
import { router, Stack, useLocalSearchParams } from "expo-router";
|
||||
import { useEffect, useState } from "react";
|
||||
import { Pressable, SafeAreaView, ScrollView, View } from "react-native";
|
||||
import { Pressable, ScrollView, View } from "react-native";
|
||||
import Toast from "react-native-toast-message";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
|
||||
@@ -94,7 +94,7 @@ export default function AddMemberTask() {
|
||||
|
||||
|
||||
return (
|
||||
<SafeAreaView>
|
||||
<>
|
||||
<Stack.Screen
|
||||
options={{
|
||||
headerLeft: () => <ButtonBackHeader onPress={() => { router.back() }} />,
|
||||
@@ -111,14 +111,14 @@ export default function AddMemberTask() {
|
||||
)
|
||||
}}
|
||||
/>
|
||||
<View style={[Styles.p15, Styles.mb100]}>
|
||||
<View style={[Styles.p15, { flex: 1 }]}>
|
||||
<InputSearch onChange={(val) => setSearch(val)} value={search} />
|
||||
|
||||
{
|
||||
selectMember.length > 0
|
||||
?
|
||||
<View>
|
||||
<ScrollView horizontal style={[Styles.mb10, Styles.pv10]}>
|
||||
<ScrollView horizontal style={[Styles.mb10, Styles.pv10]} showsHorizontalScrollIndicator={false}>
|
||||
{
|
||||
selectMember.map((item: any, index: any) => (
|
||||
<ImageWithLabel
|
||||
@@ -135,7 +135,9 @@ export default function AddMemberTask() {
|
||||
:
|
||||
<Text style={[Styles.textDefault, Styles.cGray, Styles.pv05, { textAlign: 'center' }]}>Tidak ada member yang dipilih</Text>
|
||||
}
|
||||
<ScrollView>
|
||||
<ScrollView
|
||||
showsVerticalScrollIndicator={false}
|
||||
>
|
||||
|
||||
{
|
||||
data.length > 0 ?
|
||||
@@ -170,6 +172,6 @@ export default function AddMemberTask() {
|
||||
}
|
||||
</ScrollView>
|
||||
</View>
|
||||
</SafeAreaView>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -125,7 +125,7 @@ export default function DetailTaskDivision() {
|
||||
<SectionTanggalTugasTask refreshing={refreshing} isMemberDivision={isMemberDivision} />
|
||||
<SectionFileTask refreshing={refreshing} isMemberDivision={isMemberDivision} />
|
||||
<SectionLinkTask refreshing={refreshing} isMemberDivision={isMemberDivision} />
|
||||
<SectionMemberTask refreshing={refreshing} isMemberDivision={isMemberDivision} />
|
||||
<SectionMemberTask refreshing={refreshing} isAdminDivision={isAdminDivision} />
|
||||
</View>
|
||||
</ScrollView>
|
||||
</SafeAreaView>
|
||||
|
||||
@@ -12,7 +12,7 @@ import { useAuthSession } from "@/providers/AuthProvider";
|
||||
import { AntDesign } from "@expo/vector-icons";
|
||||
import { router, Stack, useLocalSearchParams } from "expo-router";
|
||||
import { useEffect, useState } from "react";
|
||||
import { Pressable, SafeAreaView, ScrollView, View } from "react-native";
|
||||
import { Pressable, ScrollView, View } from "react-native";
|
||||
import Toast from "react-native-toast-message";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
|
||||
@@ -64,7 +64,7 @@ export default function AddMemberCreateTask() {
|
||||
|
||||
|
||||
return (
|
||||
<SafeAreaView>
|
||||
<>
|
||||
<Stack.Screen
|
||||
options={{
|
||||
headerLeft: () => <ButtonBackHeader onPress={() => { router.back() }} />,
|
||||
@@ -81,14 +81,14 @@ export default function AddMemberCreateTask() {
|
||||
)
|
||||
}}
|
||||
/>
|
||||
<View style={[Styles.p15]}>
|
||||
<View style={[Styles.p15, { flex: 1 }]}>
|
||||
<InputSearch onChange={(val) => setSearch(val)} value={search} />
|
||||
|
||||
{
|
||||
selectMember.length > 0
|
||||
?
|
||||
<View>
|
||||
<ScrollView horizontal style={[Styles.mb10, Styles.pv10]}>
|
||||
<ScrollView horizontal style={[Styles.mb10, Styles.pv10]} showsHorizontalScrollIndicator={false}>
|
||||
{
|
||||
selectMember.map((item: any, index: any) => (
|
||||
<ImageWithLabel
|
||||
@@ -105,7 +105,9 @@ export default function AddMemberCreateTask() {
|
||||
:
|
||||
<Text style={[Styles.textDefault, Styles.cGray, Styles.pv05, { textAlign: 'center' }]}>Tidak ada member yang dipilih</Text>
|
||||
}
|
||||
<ScrollView>
|
||||
<ScrollView
|
||||
showsVerticalScrollIndicator={false}
|
||||
>
|
||||
|
||||
{
|
||||
data.length > 0 ?
|
||||
@@ -136,6 +138,6 @@ export default function AddMemberCreateTask() {
|
||||
}
|
||||
</ScrollView>
|
||||
</View>
|
||||
</SafeAreaView>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -99,14 +99,18 @@ export default function CreateTaskAddTugas() {
|
||||
timeStart: item.timeStart,
|
||||
timeEnd: item.timeEnd,
|
||||
}))
|
||||
dispatch(setTaskCreate([...taskCreate, {
|
||||
const hasilOrder = [...taskCreate, {
|
||||
title: title,
|
||||
dateStart: from,
|
||||
dateEnd: to,
|
||||
dateStartFix: formatDateOnly(range.startDate, "YYYY-MM-DD"),
|
||||
dateEndFix: formatDateOnly(range.endDate, "YYYY-MM-DD"),
|
||||
dataDetail: dataDetailFix
|
||||
}]))
|
||||
}].sort((a, b) => {
|
||||
return new Date(a.dateStartFix).getTime() - new Date(b.dateStartFix).getTime();
|
||||
});
|
||||
|
||||
dispatch(setTaskCreate(hasilOrder))
|
||||
router.back();
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
|
||||
@@ -106,7 +106,7 @@ export default function ListTask() {
|
||||
return (
|
||||
<View style={[Styles.p15, { flex: 1 }]}>
|
||||
<View>
|
||||
<ScrollView horizontal style={[Styles.mb10]}>
|
||||
<ScrollView horizontal style={[Styles.mb10]} showsHorizontalScrollIndicator={false}>
|
||||
<ButtonTab
|
||||
active={statusFix}
|
||||
value="0"
|
||||
|
||||
@@ -45,6 +45,8 @@ export default function AddMemberDivision() {
|
||||
setData(responsemember.data.filter((i: any) => i.idUserRole != 'supadmin'))
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,7 +97,7 @@ export default function AddMemberDivision() {
|
||||
|
||||
|
||||
return (
|
||||
<SafeAreaView>
|
||||
<>
|
||||
<Stack.Screen
|
||||
options={{
|
||||
headerLeft: () => <ButtonBackHeader onPress={() => { router.back() }} />,
|
||||
@@ -112,14 +114,14 @@ export default function AddMemberDivision() {
|
||||
)
|
||||
}}
|
||||
/>
|
||||
<View style={[Styles.p15]}>
|
||||
<View style={[Styles.p15, { flex: 1 }]}>
|
||||
<InputSearch onChange={(val) => handleSearch(val)} value={search} />
|
||||
|
||||
{
|
||||
selectMember.length > 0
|
||||
?
|
||||
<View>
|
||||
<ScrollView horizontal style={[Styles.mb10, Styles.pv10]}>
|
||||
<ScrollView horizontal style={[Styles.mb10, Styles.pv10]} showsHorizontalScrollIndicator={false}>
|
||||
{
|
||||
selectMember.map((item: any, index: any) => (
|
||||
<ImageWithLabel
|
||||
@@ -136,7 +138,9 @@ export default function AddMemberDivision() {
|
||||
:
|
||||
<Text style={[Styles.textDefault, Styles.cGray, Styles.pv05, { textAlign: 'center' }]}>Tidak ada member yang dipilih</Text>
|
||||
}
|
||||
<ScrollView>
|
||||
<ScrollView
|
||||
showsVerticalScrollIndicator={false}
|
||||
>
|
||||
|
||||
{
|
||||
data.length > 0 ?
|
||||
@@ -171,6 +175,6 @@ export default function AddMemberDivision() {
|
||||
}
|
||||
</ScrollView>
|
||||
</View>
|
||||
</SafeAreaView>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -10,7 +10,7 @@ import { apiGetDivisionOneDetail } from "@/lib/api"
|
||||
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 { RefreshControl, SafeAreaView, ScrollView, View } from "react-native"
|
||||
|
||||
type Props = {
|
||||
id: string,
|
||||
@@ -26,11 +26,12 @@ export default function DetailDivisionFitur() {
|
||||
const { id } = useLocalSearchParams<{ id: string }>()
|
||||
const [data, setData] = useState<Props>()
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [refreshing, setRefreshing] = useState(false)
|
||||
|
||||
|
||||
async function handleLoad() {
|
||||
async function handleLoad(loading: boolean) {
|
||||
try {
|
||||
setLoading(true)
|
||||
setLoading(loading)
|
||||
const hasil = await decryptToken(String(token?.current))
|
||||
const response = await apiGetDivisionOneDetail({ user: hasil, id })
|
||||
setData(response.data.division)
|
||||
@@ -42,9 +43,16 @@ export default function DetailDivisionFitur() {
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
handleLoad()
|
||||
handleLoad(true)
|
||||
}, [])
|
||||
|
||||
const handleRefresh = async () => {
|
||||
setRefreshing(true)
|
||||
handleLoad(false)
|
||||
await new Promise(resolve => setTimeout(resolve, 2000));
|
||||
setRefreshing(false)
|
||||
};
|
||||
|
||||
return (
|
||||
<SafeAreaView>
|
||||
<Stack.Screen
|
||||
@@ -55,13 +63,21 @@ export default function DetailDivisionFitur() {
|
||||
headerRight: () => <HeaderRightDivisionDetail id={id} />,
|
||||
}}
|
||||
/>
|
||||
<ScrollView>
|
||||
<CaraouselHome />
|
||||
<ScrollView
|
||||
refreshControl={
|
||||
<RefreshControl
|
||||
refreshing={refreshing}
|
||||
onRefresh={handleRefresh}
|
||||
/>
|
||||
}
|
||||
showsVerticalScrollIndicator={false}
|
||||
>
|
||||
<CaraouselHome refreshing={refreshing} />
|
||||
<View style={[Styles.ph15, Styles.mb100]}>
|
||||
<FiturDivisionDetail />
|
||||
<TaskDivisionDetail />
|
||||
<FileDivisionDetail />
|
||||
<DiscussionDivisionDetail />
|
||||
<FiturDivisionDetail refreshing={refreshing}/>
|
||||
<TaskDivisionDetail refreshing={refreshing}/>
|
||||
<FileDivisionDetail refreshing={refreshing}/>
|
||||
<DiscussionDivisionDetail refreshing={refreshing}/>
|
||||
</View>
|
||||
</ScrollView>
|
||||
</SafeAreaView>
|
||||
|
||||
@@ -7,15 +7,16 @@ import ImageUser from "@/components/imageNew"
|
||||
import SectionCancel from "@/components/sectionCancel"
|
||||
import Skeleton from "@/components/skeleton"
|
||||
import SkeletonTwoItem from "@/components/skeletonTwoItem"
|
||||
import Text from "@/components/Text"
|
||||
import { ColorsStatus } from "@/constants/ColorsStatus"
|
||||
import { ConstEnv } from "@/constants/ConstEnv"
|
||||
import Styles from "@/constants/Styles"
|
||||
import { apiDeleteMemberDivision, apiGetDivisionOneDetail, apiUpdateStatusAdminDivision } from "@/lib/api"
|
||||
import { apiDeleteMemberDivision, apiGetDivisionOneDetail, apiGetDivisionOneFeature, apiUpdateStatusAdminDivision } from "@/lib/api"
|
||||
import { useAuthSession } from "@/providers/AuthProvider"
|
||||
import { Feather, MaterialCommunityIcons, MaterialIcons } from "@expo/vector-icons"
|
||||
import { router, Stack, useLocalSearchParams } from "expo-router"
|
||||
import { useEffect, useState } from "react"
|
||||
import { Pressable, SafeAreaView, ScrollView, Text, View } from "react-native"
|
||||
import { Pressable, RefreshControl, SafeAreaView, ScrollView, View } from "react-native"
|
||||
import Toast from "react-native-toast-message"
|
||||
import { useSelector } from "react-redux"
|
||||
|
||||
@@ -38,6 +39,8 @@ type PropsMember = {
|
||||
}
|
||||
|
||||
export default function InformationDivision() {
|
||||
const [refreshing, setRefreshing] = useState(false)
|
||||
const entityUser = useSelector((state: any) => state.user)
|
||||
const { id } = useLocalSearchParams<{ id: string }>()
|
||||
const [isModal, setModal] = useState(false)
|
||||
const { token, decryptToken } = useAuthSession()
|
||||
@@ -47,6 +50,8 @@ export default function InformationDivision() {
|
||||
const update = useSelector((state: any) => state.divisionUpdate)
|
||||
const arrSkeleton = Array.from({ length: 5 }, (_, index) => index)
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [isMemberDivision, setIsMemberDivision] = useState(false)
|
||||
const [isAdminDivision, setIsAdminDivision] = useState(false)
|
||||
const [dataMemberChoose, setDataMemberChoose] = useState({
|
||||
id: '',
|
||||
name: '',
|
||||
@@ -112,12 +117,42 @@ export default function InformationDivision() {
|
||||
}
|
||||
}
|
||||
|
||||
const handleRefresh = async () => {
|
||||
setRefreshing(true)
|
||||
handleLoad(false)
|
||||
handleCheckMember()
|
||||
await new Promise(resolve => setTimeout(resolve, 2000));
|
||||
setRefreshing(false)
|
||||
};
|
||||
|
||||
async function handleCheckMember() {
|
||||
try {
|
||||
const hasil = await decryptToken(String(token?.current));
|
||||
const response = await apiGetDivisionOneFeature({
|
||||
id,
|
||||
user: hasil,
|
||||
cat: "check-member",
|
||||
});
|
||||
|
||||
const response2 = await apiGetDivisionOneFeature({
|
||||
id,
|
||||
user: hasil,
|
||||
cat: "check-admin",
|
||||
});
|
||||
setIsMemberDivision(response.data);
|
||||
setIsAdminDivision(response2.data);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
handleLoad(false)
|
||||
}, [refresh, update])
|
||||
|
||||
useEffect(() => {
|
||||
handleLoad(true)
|
||||
handleCheckMember()
|
||||
}, [])
|
||||
|
||||
function handleChooseMember(item: PropsMember) {
|
||||
@@ -132,10 +167,18 @@ export default function InformationDivision() {
|
||||
headerLeft: () => <ButtonBackHeader onPress={() => { router.back() }} />,
|
||||
headerTitle: 'Informasi Divisi',
|
||||
headerTitleAlign: 'center',
|
||||
headerRight: () => <HeaderRightDivisionInfo id={id} active={dataDetail?.isActive} />,
|
||||
headerRight: () => ((entityUser.role != "user" && entityUser.role != "coadmin") || isAdminDivision) && <HeaderRightDivisionInfo id={id} active={dataDetail?.isActive} />,
|
||||
}}
|
||||
/>
|
||||
<ScrollView style={[Styles.h100]}>
|
||||
<ScrollView
|
||||
refreshControl={
|
||||
<RefreshControl
|
||||
refreshing={refreshing}
|
||||
onRefresh={handleRefresh}
|
||||
/>
|
||||
}
|
||||
style={[Styles.h100]}
|
||||
>
|
||||
<View style={[Styles.p15]}>
|
||||
{
|
||||
dataDetail?.isActive == false && (
|
||||
@@ -160,6 +203,7 @@ export default function InformationDivision() {
|
||||
<Text style={[Styles.textDefault, Styles.mv05]}>{dataMember.length} Anggota</Text>
|
||||
<View style={[Styles.wrapPaper]}>
|
||||
{
|
||||
((entityUser.role != "user" && entityUser.role != "coadmin") || isAdminDivision) &&
|
||||
dataDetail?.isActive && (
|
||||
<BorderBottomItem
|
||||
onPress={() => { router.push(`/division/${id}/add-member`) }}
|
||||
@@ -187,7 +231,7 @@ export default function InformationDivision() {
|
||||
<BorderBottomItem
|
||||
key={index}
|
||||
borderType="bottom"
|
||||
onPress={() => { dataDetail?.isActive && handleChooseMember(item) }}
|
||||
onPress={() => { dataDetail?.isActive && (isAdminDivision || (entityUser.role != "user" && entityUser.role != "coadmin")) && handleChooseMember(item) }}
|
||||
icon={
|
||||
<ImageUser src={`${ConstEnv.url_storage}/files/${item.img}`} size="sm" />
|
||||
}
|
||||
|
||||
@@ -1,22 +1,28 @@
|
||||
import AlertKonfirmasi from "@/components/alertKonfirmasi";
|
||||
import ButtonBackHeader from "@/components/buttonBackHeader";
|
||||
import ButtonNextHeader from "@/components/buttonNextHeader";
|
||||
import { InputForm } from "@/components/inputForm";
|
||||
import ModalSelect from "@/components/modalSelect";
|
||||
import SelectForm from "@/components/selectForm";
|
||||
import Styles from "@/constants/Styles";
|
||||
import { apiCheckDivisionName } from "@/lib/api";
|
||||
import { setFormCreateDivision } from "@/lib/divisionCreate";
|
||||
import { useAuthSession } from "@/providers/AuthProvider";
|
||||
import { router, Stack } 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 CreateDivision() {
|
||||
const [isSelect, setSelect] = useState(false);
|
||||
const [chooseGroup, setChooseGroup] = useState({ val: "", label: "" });
|
||||
const dispatch = useDispatch();
|
||||
const update = useSelector((state: any) => state.divisionCreate);
|
||||
const { token, decryptToken } = useAuthSession()
|
||||
const [isSelect, setSelect] = useState(false)
|
||||
const [chooseGroup, setChooseGroup] = useState({ val: "", label: "" })
|
||||
const dispatch = useDispatch()
|
||||
const update = useSelector((state: any) => state.divisionCreate)
|
||||
const entityUser = useSelector((state: any) => state.user)
|
||||
const userLogin = useSelector((state: any) => state.entities)
|
||||
const [loadingBtn, setLoadingBtn] = useState(false)
|
||||
const [error, setError] = useState({
|
||||
idGroup: false,
|
||||
name: false,
|
||||
@@ -54,7 +60,35 @@ export default function CreateDivision() {
|
||||
}
|
||||
}
|
||||
|
||||
function handleSetData() {
|
||||
async function handleCheckName() {
|
||||
try {
|
||||
setLoadingBtn(true)
|
||||
const hasil = await decryptToken(String(token?.current))
|
||||
const response = await apiCheckDivisionName({ data: { ...dataForm }, user: hasil })
|
||||
if (response.success) {
|
||||
if (!response.available) {
|
||||
AlertKonfirmasi({
|
||||
title: 'Peringatan',
|
||||
desc: 'Nama divisi sudah ada. Apakah anda yakin ingin membuat divisi dengan nama yang sama?',
|
||||
onPress: () => {
|
||||
handleSetData()
|
||||
}
|
||||
})
|
||||
} else {
|
||||
handleSetData()
|
||||
}
|
||||
} else {
|
||||
Toast.show({ type: 'small', text1: response.message, })
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
Toast.show({ type: 'small', text1: 'Terjadi kesalahan', })
|
||||
} finally {
|
||||
setLoadingBtn(false)
|
||||
}
|
||||
}
|
||||
|
||||
async function handleSetData() {
|
||||
dispatch(setFormCreateDivision({ ...update, data: dataForm }))
|
||||
router.push(`./create/add-member`)
|
||||
}
|
||||
@@ -80,8 +114,8 @@ export default function CreateDivision() {
|
||||
headerTitleAlign: "center",
|
||||
headerRight: () => (
|
||||
<ButtonNextHeader
|
||||
onPress={() => { handleSetData() }}
|
||||
disable={error.idGroup || error.name || chooseGroup.val == "" || chooseGroup.val == "null" || dataForm.name == "" || dataForm.name == "null"}
|
||||
onPress={() => { handleCheckName() }}
|
||||
disable={loadingBtn || error.idGroup || error.name || chooseGroup.val == "" || chooseGroup.val == "null" || dataForm.name == "" || dataForm.name == "null"}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
|
||||
@@ -9,6 +9,7 @@ import { setFormCreateDivision } from "@/lib/divisionCreate";
|
||||
import { setUpdateDivision } from "@/lib/divisionUpdate";
|
||||
import { useAuthSession } from "@/providers/AuthProvider";
|
||||
import { AntDesign } from "@expo/vector-icons";
|
||||
import { StackActions, useNavigation } from "@react-navigation/native";
|
||||
import { router, Stack, useLocalSearchParams } from "expo-router";
|
||||
import { useEffect, useState } from "react";
|
||||
import { Pressable, SafeAreaView, ScrollView, View } from "react-native";
|
||||
@@ -23,6 +24,7 @@ type Props = {
|
||||
|
||||
export default function CreateDivisionAddAdmin() {
|
||||
const { token, decryptToken } = useAuthSession()
|
||||
const navigation = useNavigation()
|
||||
const { id } = useLocalSearchParams<{ id: string }>()
|
||||
const [dataOld, setDataOld] = useState<Props[]>([])
|
||||
const [data, setData] = useState<Props[]>([])
|
||||
@@ -51,14 +53,14 @@ 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 })
|
||||
const response = await apiCreateDivision({ ...update, admin: selectMember, user: hasil })
|
||||
if (response.success) {
|
||||
Toast.show({ type: 'small', text1: 'Berhasil membuat divisi', })
|
||||
dispatch(setFormCreateDivision({ admin: [], member: [], data: { idGroup: '', name: '', desc: '' } }))
|
||||
dispatch(setUpdateDivision(!updateDivision))
|
||||
router.replace(`/division/`)
|
||||
navigation.dispatch(StackActions.pop(3))
|
||||
// router.replace(`/division/`)
|
||||
} else {
|
||||
Toast.show({ type: 'small', text1: response.message, })
|
||||
}
|
||||
@@ -72,7 +74,7 @@ export default function CreateDivisionAddAdmin() {
|
||||
|
||||
|
||||
return (
|
||||
<SafeAreaView>
|
||||
<>
|
||||
<Stack.Screen
|
||||
options={{
|
||||
headerLeft: () => <ButtonBackHeader onPress={() => { router.back() }} />,
|
||||
@@ -89,7 +91,7 @@ export default function CreateDivisionAddAdmin() {
|
||||
)
|
||||
}}
|
||||
/>
|
||||
<View style={[Styles.p15]}>
|
||||
<View style={[Styles.p15, { flex: 1 }]}>
|
||||
<ScrollView>
|
||||
{
|
||||
data.length > 0 ?
|
||||
@@ -124,6 +126,6 @@ export default function CreateDivisionAddAdmin() {
|
||||
}
|
||||
</ScrollView>
|
||||
</View>
|
||||
</SafeAreaView>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -12,7 +12,7 @@ import { useAuthSession } from "@/providers/AuthProvider";
|
||||
import { AntDesign } from "@expo/vector-icons";
|
||||
import { router, Stack, useLocalSearchParams } from "expo-router";
|
||||
import { useEffect, useState } from "react";
|
||||
import { Pressable, SafeAreaView, ScrollView, View } from "react-native";
|
||||
import { Pressable, ScrollView, View } from "react-native";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
|
||||
type Props = {
|
||||
@@ -60,7 +60,7 @@ export default function CreateDivisionAddMember() {
|
||||
|
||||
|
||||
return (
|
||||
<SafeAreaView>
|
||||
<>
|
||||
<Stack.Screen
|
||||
options={{
|
||||
headerLeft: () => <ButtonBackHeader onPress={() => { router.back() }} />,
|
||||
@@ -74,14 +74,14 @@ export default function CreateDivisionAddMember() {
|
||||
)
|
||||
}}
|
||||
/>
|
||||
<View style={[Styles.p15]}>
|
||||
<View style={[Styles.p15, { flex: 1 }]}>
|
||||
<InputSearch onChange={(val) => setSearch(val)} value={search} />
|
||||
|
||||
{
|
||||
selectMember.length > 0
|
||||
?
|
||||
<View>
|
||||
<ScrollView horizontal style={[Styles.mb10, Styles.pv10]}>
|
||||
<ScrollView horizontal style={[Styles.mb10, Styles.pv10]} showsHorizontalScrollIndicator={false}>
|
||||
{
|
||||
selectMember.map((item: any, index: any) => (
|
||||
<ImageWithLabel
|
||||
@@ -98,7 +98,9 @@ export default function CreateDivisionAddMember() {
|
||||
:
|
||||
<Text style={[Styles.textDefault, Styles.cGray, Styles.pv05, { textAlign: 'center' }]}>Tidak ada member yang dipilih</Text>
|
||||
}
|
||||
<ScrollView>
|
||||
<ScrollView
|
||||
showsVerticalScrollIndicator={false}
|
||||
>
|
||||
|
||||
{
|
||||
data.length > 0 ?
|
||||
@@ -133,6 +135,6 @@ export default function CreateDivisionAddMember() {
|
||||
}
|
||||
</ScrollView>
|
||||
</View>
|
||||
</SafeAreaView>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -8,13 +8,17 @@ import { ConstEnv } from "@/constants/ConstEnv";
|
||||
import Styles from "@/constants/Styles";
|
||||
import { apiEditProfile, apiGetProfile } from "@/lib/api";
|
||||
import { setEntities } from "@/lib/entitiesSlice";
|
||||
import { validateName } from "@/lib/fun_validateName";
|
||||
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";
|
||||
import {
|
||||
Image,
|
||||
KeyboardAvoidingView,
|
||||
Platform,
|
||||
Pressable,
|
||||
SafeAreaView,
|
||||
ScrollView,
|
||||
@@ -37,6 +41,7 @@ type Props = {
|
||||
};
|
||||
|
||||
export default function EditProfile() {
|
||||
const headerHeight = useHeaderHeight()
|
||||
const dispatch = useDispatch()
|
||||
const entities = useSelector((state: any) => state.entities)
|
||||
const { token, decryptToken } = useAuthSession()
|
||||
@@ -105,7 +110,7 @@ export default function EditProfile() {
|
||||
}
|
||||
} else if (cat == "name") {
|
||||
setData({ ...data, name: val });
|
||||
if (val == "") {
|
||||
if (!validateName(val)) {
|
||||
setError({ ...error, name: true });
|
||||
} else {
|
||||
setError({ ...error, name: false });
|
||||
@@ -122,7 +127,7 @@ export default function EditProfile() {
|
||||
}
|
||||
} else if (cat == "phone") {
|
||||
setData({ ...data, phone: val });
|
||||
if (val == "" || !(val.length >= 10 && val.length <= 15)) {
|
||||
if (val == "" || !(val.length >= 9 && val.length <= 16)) {
|
||||
setError({ ...error, phone: true });
|
||||
} else {
|
||||
setError({ ...error, phone: false });
|
||||
@@ -160,8 +165,8 @@ export default function EditProfile() {
|
||||
if (imgForm != undefined) {
|
||||
fd.append("file", {
|
||||
uri: imgForm.uri,
|
||||
type: imgForm.mimeType,
|
||||
name: imgForm.fileName,
|
||||
type: imgForm.mimeType || "image/jpeg",
|
||||
name: imgForm.fileName || "image.jpg",
|
||||
} as any);
|
||||
} else {
|
||||
fd.append("file", "undefined",);
|
||||
@@ -193,9 +198,9 @@ export default function EditProfile() {
|
||||
const pickImageAsync = async () => {
|
||||
let result = await ImagePicker.launchImageLibraryAsync({
|
||||
mediaTypes: ["images"],
|
||||
allowsEditing: false,
|
||||
quality: 1,
|
||||
aspect: [1, 1],
|
||||
allowsEditing: true,
|
||||
quality: 0.9,
|
||||
aspect: [1, 1]
|
||||
});
|
||||
|
||||
if (!result.canceled) {
|
||||
@@ -231,116 +236,122 @@ export default function EditProfile() {
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<ScrollView style={[Styles.h100]}>
|
||||
<View style={[Styles.p15]}>
|
||||
<View style={{ justifyContent: "center", alignItems: "center" }}>
|
||||
{
|
||||
selectedImage != undefined ? (
|
||||
<Pressable onPress={pickImageAsync}>
|
||||
<Image
|
||||
src={
|
||||
typeof selectedImage === "string"
|
||||
? selectedImage
|
||||
: selectedImage.uri
|
||||
}
|
||||
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: `${ConstEnv.url_storage}/files/${data?.img}` }}
|
||||
style={[Styles.userProfileBig]}
|
||||
onError={() => { setErrorImg(true) }}
|
||||
/>
|
||||
<View style={[Styles.absoluteIconPicker]}>
|
||||
<MaterialCommunityIcons name="image" color={'white'} size={15} />
|
||||
</View>
|
||||
</Pressable>
|
||||
)
|
||||
}
|
||||
<KeyboardAvoidingView
|
||||
style={[Styles.h100]}
|
||||
behavior={Platform.OS === 'ios' ? 'padding' : undefined}
|
||||
keyboardVerticalOffset={headerHeight}
|
||||
>
|
||||
<ScrollView showsVerticalScrollIndicator={false}>
|
||||
<View style={[Styles.p15]}>
|
||||
<View style={{ justifyContent: "center", alignItems: "center" }}>
|
||||
{
|
||||
selectedImage != undefined ? (
|
||||
<Pressable onPress={pickImageAsync}>
|
||||
<Image
|
||||
src={
|
||||
typeof selectedImage === "string"
|
||||
? selectedImage
|
||||
: selectedImage.uri
|
||||
}
|
||||
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: `${ConstEnv.url_storage}/files/${data?.img}` }}
|
||||
style={[Styles.userProfileBig]}
|
||||
onError={() => { setErrorImg(true) }}
|
||||
/>
|
||||
<View style={[Styles.absoluteIconPicker]}>
|
||||
<MaterialCommunityIcons name="image" color={'white'} size={15} />
|
||||
</View>
|
||||
</Pressable>
|
||||
)
|
||||
}
|
||||
</View>
|
||||
<SelectForm
|
||||
label="Jabatan"
|
||||
placeholder="Pilih Jabatan"
|
||||
value={choosePosition.label}
|
||||
required
|
||||
onPress={() => {
|
||||
setValChoose(choosePosition.val);
|
||||
setValSelect("position");
|
||||
setSelect(true);
|
||||
}}
|
||||
error={error.position}
|
||||
errorText="Jabatan tidak boleh kosong"
|
||||
/>
|
||||
<InputForm
|
||||
label="NIK"
|
||||
type="numeric"
|
||||
placeholder="NIK"
|
||||
required
|
||||
value={data?.nik}
|
||||
error={error.nik}
|
||||
errorText="NIK Harus 16 Karakter"
|
||||
onChange={val => {
|
||||
validationForm("nik", val)
|
||||
}}
|
||||
/>
|
||||
<InputForm
|
||||
label="Nama"
|
||||
type="default"
|
||||
placeholder="Nama"
|
||||
required
|
||||
value={data?.name}
|
||||
error={error.name}
|
||||
errorText="Nama harus 3–50 karakter (huruf, angka, spasi, dan simbol ringan (. , ' _ -))"
|
||||
onChange={val => {
|
||||
validationForm("name", val)
|
||||
}}
|
||||
/>
|
||||
<InputForm
|
||||
label="Email"
|
||||
type="default"
|
||||
placeholder="Email"
|
||||
required
|
||||
value={data?.email}
|
||||
error={error.email}
|
||||
errorText="Email tidak valid"
|
||||
onChange={val => {
|
||||
validationForm("email", val)
|
||||
}}
|
||||
/>
|
||||
<InputForm
|
||||
label="Nomor Telepon"
|
||||
type="numeric"
|
||||
placeholder="8XX-XXX-XXX"
|
||||
required
|
||||
itemLeft={<Text>+62</Text>}
|
||||
value={data?.phone}
|
||||
error={error.phone}
|
||||
errorText="Nomor Telepon tidak valid"
|
||||
onChange={val => {
|
||||
validationForm("phone", val)
|
||||
}}
|
||||
/>
|
||||
<SelectForm
|
||||
label="Jenis Kelamin"
|
||||
placeholder="Pilih Jenis Kelamin"
|
||||
value={chooseGender.label}
|
||||
required
|
||||
onPress={() => {
|
||||
setValChoose(chooseGender.val);
|
||||
setValSelect("gender");
|
||||
setSelect(true);
|
||||
}}
|
||||
error={error.gender}
|
||||
errorText="Jenis Kelamin tidak boleh kosong"
|
||||
/>
|
||||
</View>
|
||||
<SelectForm
|
||||
label="Jabatan"
|
||||
placeholder="Pilih Jabatan"
|
||||
value={choosePosition.label}
|
||||
required
|
||||
onPress={() => {
|
||||
setValChoose(choosePosition.val);
|
||||
setValSelect("position");
|
||||
setSelect(true);
|
||||
}}
|
||||
error={error.position}
|
||||
errorText="Jabatan tidak boleh kosong"
|
||||
/>
|
||||
<InputForm
|
||||
label="NIK"
|
||||
type="numeric"
|
||||
placeholder="NIK"
|
||||
required
|
||||
value={data?.nik}
|
||||
error={error.nik}
|
||||
errorText="NIK Harus 16 Karakter"
|
||||
onChange={val => {
|
||||
validationForm("nik", val)
|
||||
}}
|
||||
/>
|
||||
<InputForm
|
||||
label="Nama"
|
||||
type="default"
|
||||
placeholder="Nama"
|
||||
required
|
||||
value={data?.name}
|
||||
error={error.name}
|
||||
errorText="Nama tidak boleh kosong"
|
||||
onChange={val => {
|
||||
validationForm("name", val)
|
||||
}}
|
||||
/>
|
||||
<InputForm
|
||||
label="Email"
|
||||
type="default"
|
||||
placeholder="Email"
|
||||
required
|
||||
value={data?.email}
|
||||
error={error.email}
|
||||
errorText="Email tidak valid"
|
||||
onChange={val => {
|
||||
validationForm("email", val)
|
||||
}}
|
||||
/>
|
||||
<InputForm
|
||||
label="Nomor Telepon"
|
||||
type="numeric"
|
||||
placeholder="8XX-XXX-XXX"
|
||||
required
|
||||
itemLeft={<Text>+62</Text>}
|
||||
value={data?.phone}
|
||||
error={error.phone}
|
||||
errorText="Nomor Telepon tidak valid"
|
||||
onChange={val => {
|
||||
validationForm("phone", val)
|
||||
}}
|
||||
/>
|
||||
<SelectForm
|
||||
label="Jenis Kelamin"
|
||||
placeholder="Pilih Jenis Kelamin"
|
||||
value={chooseGender.label}
|
||||
required
|
||||
onPress={() => {
|
||||
setValChoose(chooseGender.val);
|
||||
setValSelect("gender");
|
||||
setSelect(true);
|
||||
}}
|
||||
error={error.gender}
|
||||
errorText="Jenis Kelamin tidak boleh kosong"
|
||||
/>
|
||||
</View>
|
||||
</ScrollView>
|
||||
</ScrollView>
|
||||
</KeyboardAvoidingView>
|
||||
|
||||
<ModalSelect
|
||||
category={valSelect}
|
||||
|
||||
@@ -13,8 +13,8 @@ import { apiGetProfile } from "@/lib/api";
|
||||
import { setEntities } from "@/lib/entitiesSlice";
|
||||
import { useAuthSession } from "@/providers/AuthProvider";
|
||||
import { Stack } from "expo-router";
|
||||
import { useEffect } from "react";
|
||||
import { Platform, SafeAreaView, ScrollView, View } from "react-native";
|
||||
import { useEffect, useState } from "react";
|
||||
import { Platform, RefreshControl, SafeAreaView, ScrollView, View } from "react-native";
|
||||
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
|
||||
@@ -24,6 +24,7 @@ export default function Home() {
|
||||
const dispatch = useDispatch()
|
||||
const { token, decryptToken, signOut } = useAuthSession()
|
||||
const insets = useSafeAreaInsets()
|
||||
const [refreshing, setRefreshing] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
handleUserLogin()
|
||||
@@ -38,6 +39,13 @@ export default function Home() {
|
||||
});
|
||||
}
|
||||
|
||||
const handleRefresh = async () => {
|
||||
setRefreshing(true)
|
||||
handleUserLogin()
|
||||
await new Promise(resolve => setTimeout(resolve, 2000));
|
||||
setRefreshing(false)
|
||||
};
|
||||
|
||||
return (
|
||||
<SafeAreaView>
|
||||
<Stack.Screen
|
||||
@@ -52,16 +60,24 @@ export default function Home() {
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<ScrollView>
|
||||
<CaraouselHome />
|
||||
<ScrollView
|
||||
refreshControl={
|
||||
<RefreshControl
|
||||
refreshing={refreshing}
|
||||
onRefresh={handleRefresh}
|
||||
/>
|
||||
}
|
||||
showsVerticalScrollIndicator={false}
|
||||
>
|
||||
<CaraouselHome refreshing={refreshing}/>
|
||||
<View style={[Styles.ph15, Styles.mb100]}>
|
||||
<FiturHome />
|
||||
<ProjectHome />
|
||||
<DivisionHome />
|
||||
<ChartProgresHome />
|
||||
<ChartDokumenHome />
|
||||
<EventHome />
|
||||
<DisccussionHome />
|
||||
<ProjectHome refreshing={refreshing}/>
|
||||
<DivisionHome refreshing={refreshing}/>
|
||||
<ChartProgresHome refreshing={refreshing}/>
|
||||
<ChartDokumenHome refreshing={refreshing}/>
|
||||
<EventHome refreshing={refreshing}/>
|
||||
<DisccussionHome refreshing={refreshing}/>
|
||||
</View>
|
||||
</ScrollView>
|
||||
</SafeAreaView>
|
||||
|
||||
@@ -7,6 +7,7 @@ import Text from "@/components/Text";
|
||||
import { ColorsStatus } from "@/constants/ColorsStatus";
|
||||
import Styles from "@/constants/Styles";
|
||||
import { apiCreateUser } from "@/lib/api";
|
||||
import { validateName } from "@/lib/fun_validateName";
|
||||
import { setUpdateMember } from "@/lib/memberSlice";
|
||||
import { useAuthSession } from "@/providers/AuthProvider";
|
||||
import { MaterialCommunityIcons } from "@expo/vector-icons";
|
||||
@@ -100,7 +101,7 @@ export default function CreateMember() {
|
||||
}
|
||||
} else if (cat == "name") {
|
||||
setDataForm({ ...dataForm, name: val });
|
||||
if (val == "") {
|
||||
if (!validateName(val)) {
|
||||
setError({ ...error, name: true });
|
||||
} else {
|
||||
setError({ ...error, name: false });
|
||||
@@ -117,7 +118,7 @@ export default function CreateMember() {
|
||||
}
|
||||
} else if (cat == "phone") {
|
||||
setDataForm({ ...dataForm, phone: val });
|
||||
if (val == "" || !(val.length >= 10 && val.length <= 15)) {
|
||||
if (val == "" || !(val.length >= 9 && val.length <= 16)) {
|
||||
setError({ ...error, phone: true });
|
||||
} else {
|
||||
setError({ ...error, phone: false });
|
||||
@@ -166,8 +167,8 @@ export default function CreateMember() {
|
||||
if (imgForm != undefined) {
|
||||
fd.append("file", {
|
||||
uri: imgForm.uri,
|
||||
type: imgForm.mimeType,
|
||||
name: imgForm.fileName,
|
||||
type: imgForm.mimeType || "image/jpeg",
|
||||
name: imgForm.fileName || "image.jpg",
|
||||
} as any)
|
||||
} else {
|
||||
fd.append("file", "undefined")
|
||||
@@ -193,9 +194,9 @@ export default function CreateMember() {
|
||||
const pickImageAsync = async () => {
|
||||
let result = await ImagePicker.launchImageLibraryAsync({
|
||||
mediaTypes: ["images"],
|
||||
allowsEditing: false,
|
||||
quality: 1,
|
||||
aspect: [1, 1],
|
||||
allowsEditing: true,
|
||||
quality: 0.9,
|
||||
aspect: [1, 1]
|
||||
});
|
||||
|
||||
if (!result.canceled) {
|
||||
@@ -231,7 +232,7 @@ export default function CreateMember() {
|
||||
behavior={Platform.OS === 'ios' ? 'padding' : undefined}
|
||||
keyboardVerticalOffset={headerHeight}
|
||||
>
|
||||
<ScrollView>
|
||||
<ScrollView showsVerticalScrollIndicator={false}>
|
||||
<View style={[Styles.p15]}>
|
||||
<View style={{ justifyContent: "center", alignItems: "center" }}>
|
||||
{selectedImage != undefined ? (
|
||||
@@ -309,7 +310,7 @@ export default function CreateMember() {
|
||||
placeholder="Nama"
|
||||
required
|
||||
error={error.name}
|
||||
errorText="Nama tidak boleh kosong"
|
||||
errorText="Nama harus 3–50 karakter (huruf, angka, spasi, dan simbol ringan (. , ' _ -))"
|
||||
onChange={val => {
|
||||
validationForm("name", val)
|
||||
}}
|
||||
|
||||
@@ -7,6 +7,7 @@ import Text from "@/components/Text";
|
||||
import { ConstEnv } from "@/constants/ConstEnv";
|
||||
import Styles from "@/constants/Styles";
|
||||
import { apiEditUser, apiGetProfile } from "@/lib/api";
|
||||
import { validateName } from "@/lib/fun_validateName";
|
||||
import { setUpdateMember } from "@/lib/memberSlice";
|
||||
import { useAuthSession } from "@/providers/AuthProvider";
|
||||
import { MaterialCommunityIcons } from "@expo/vector-icons";
|
||||
@@ -132,7 +133,7 @@ export default function EditMember() {
|
||||
}
|
||||
} else if (cat == "name") {
|
||||
setData({ ...data, name: val });
|
||||
if (val == "") {
|
||||
if (!validateName(val)) {
|
||||
setError({ ...error, name: true });
|
||||
} else {
|
||||
setError({ ...error, name: false });
|
||||
@@ -149,7 +150,7 @@ export default function EditMember() {
|
||||
}
|
||||
} else if (cat == "phone") {
|
||||
setData({ ...data, phone: val });
|
||||
if (val == "" || !(val.length >= 10 && val.length <= 15)) {
|
||||
if (val == "" || !(val.length >= 9 && val.length <= 16)) {
|
||||
setError({ ...error, phone: true });
|
||||
} else {
|
||||
setError({ ...error, phone: false });
|
||||
@@ -187,8 +188,8 @@ export default function EditMember() {
|
||||
if (imgForm != undefined) {
|
||||
fd.append("file", {
|
||||
uri: imgForm.uri,
|
||||
type: imgForm.mimeType,
|
||||
name: imgForm.fileName,
|
||||
type: imgForm.mimeType || "image/jpeg",
|
||||
name: imgForm.fileName || "image.jpg",
|
||||
} as any);
|
||||
} else {
|
||||
fd.append("file", "undefined",);
|
||||
@@ -220,9 +221,9 @@ export default function EditMember() {
|
||||
const pickImageAsync = async () => {
|
||||
let result = await ImagePicker.launchImageLibraryAsync({
|
||||
mediaTypes: ["images"],
|
||||
allowsEditing: false,
|
||||
quality: 1,
|
||||
aspect: [1, 1],
|
||||
allowsEditing: true,
|
||||
quality: 0.9,
|
||||
aspect: [1, 1]
|
||||
});
|
||||
|
||||
if (!result.canceled) {
|
||||
@@ -264,8 +265,8 @@ export default function EditMember() {
|
||||
behavior={Platform.OS === 'ios' ? 'padding' : undefined}
|
||||
keyboardVerticalOffset={headerHeight}
|
||||
>
|
||||
<ScrollView>
|
||||
<View style={[Styles.p15, Styles.mb100]}>
|
||||
<ScrollView showsVerticalScrollIndicator={false} style={[Styles.h100]}>
|
||||
<View style={[Styles.p15]}>
|
||||
<View style={{ justifyContent: "center", alignItems: "center" }}>
|
||||
{
|
||||
errorImg ?
|
||||
@@ -348,7 +349,7 @@ export default function EditMember() {
|
||||
required
|
||||
value={data?.name}
|
||||
error={error.name}
|
||||
errorText="Nama tidak boleh kosong"
|
||||
errorText="Nama harus 3–50 karakter (huruf, angka, spasi, dan simbol ringan (. , ' _ -))"
|
||||
onChange={val => {
|
||||
validationForm("name", val)
|
||||
}}
|
||||
|
||||
@@ -9,7 +9,7 @@ import { pushToPage } from "@/lib/pushToPage";
|
||||
import { useAuthSession } from "@/providers/AuthProvider";
|
||||
import { Feather } from "@expo/vector-icons";
|
||||
import { useEffect, useState } from "react";
|
||||
import { SafeAreaView, View, VirtualizedList } from "react-native";
|
||||
import { RefreshControl, SafeAreaView, View, VirtualizedList } from "react-native";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
|
||||
type Props = {
|
||||
@@ -31,6 +31,7 @@ export default function Notification() {
|
||||
const arrSkeleton = Array.from({ length: 5 }, (_, index) => index)
|
||||
const dispatch = useDispatch()
|
||||
const updateNotification = useSelector((state: any) => state.notificationUpdate)
|
||||
const [refreshing, setRefreshing] = useState(false)
|
||||
|
||||
async function handleLoad(loading: boolean, thisPage: number) {
|
||||
try {
|
||||
@@ -88,33 +89,12 @@ export default function Notification() {
|
||||
}
|
||||
}
|
||||
|
||||
// function pushToPage(category: string, idContent: string) {
|
||||
// const cat = category.split('/')
|
||||
// if (cat.length > 1) {
|
||||
// if (cat[2] == 'calendar') {
|
||||
// router.push(`/division/${cat[1]}/calendar/${idContent}`)
|
||||
// } else if (cat[2] == 'discussion') {
|
||||
// router.push(`/division/${cat[1]}/discussion/${idContent}`)
|
||||
// } else if (cat[2] == 'document') {
|
||||
// router.push(`/division/${cat[1]}/document/${idContent}`)
|
||||
// } else if (cat[2] == 'task') {
|
||||
// router.push(`/division/${cat[1]}/task/${idContent}`)
|
||||
// }
|
||||
// } else {
|
||||
// if (cat[0] == 'announcement') {
|
||||
// router.push(`/announcement/${idContent}`)
|
||||
// } else if (cat[0] == 'discussion-general') {
|
||||
// router.push(`/discussion/${idContent}`)
|
||||
// } else if (cat[0] == 'division') {
|
||||
// router.push(`/division/${idContent}`)
|
||||
// } else if (cat[0] == 'member') {
|
||||
// router.push(`/member/${idContent}`)
|
||||
// } else if (cat[0] == 'project') {
|
||||
// router.push(`/project/${idContent}`)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
const handleRefresh = async () => {
|
||||
setRefreshing(true)
|
||||
handleLoad(false, 1)
|
||||
await new Promise(resolve => setTimeout(resolve, 2000));
|
||||
setRefreshing(false)
|
||||
};
|
||||
|
||||
return (
|
||||
<SafeAreaView>
|
||||
@@ -156,6 +136,12 @@ export default function Notification() {
|
||||
onEndReached={loadMoreData}
|
||||
onEndReachedThreshold={0.5}
|
||||
showsVerticalScrollIndicator={false}
|
||||
refreshControl={
|
||||
<RefreshControl
|
||||
refreshing={refreshing}
|
||||
onRefresh={handleRefresh}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
:
|
||||
<Text style={[Styles.textDefault, Styles.cGray, { textAlign: 'center' }]}>Tidak ada data</Text>
|
||||
|
||||
@@ -189,7 +189,10 @@ export default function Index() {
|
||||
return (
|
||||
<BorderBottomItem
|
||||
key={index}
|
||||
onPress={() => { handleChooseData(item.id, item.name, item.isActive, item.idGroup) }}
|
||||
onPress={() => {
|
||||
entityUser.role != "user" &&
|
||||
handleChooseData(item.id, item.name, item.isActive, item.idGroup)
|
||||
}}
|
||||
borderType="all"
|
||||
icon={
|
||||
<View style={[Styles.iconContent, ColorsStatus.lightGreen]}>
|
||||
|
||||
@@ -38,7 +38,7 @@ export default function Profile() {
|
||||
/>
|
||||
}}
|
||||
/>
|
||||
<ScrollView>
|
||||
<ScrollView style={[Styles.h100]}>
|
||||
<View style={{ flexDirection: 'column' }}>
|
||||
<View style={[Styles.wrapHeadViewMember]}>
|
||||
<Image
|
||||
|
||||
@@ -12,7 +12,7 @@ import { useAuthSession } from "@/providers/AuthProvider";
|
||||
import { AntDesign } from "@expo/vector-icons";
|
||||
import { router, Stack, useLocalSearchParams } from "expo-router";
|
||||
import { useEffect, useState } from "react";
|
||||
import { Pressable, SafeAreaView, ScrollView, View } from "react-native";
|
||||
import { Pressable, ScrollView, View } from "react-native";
|
||||
import Toast from "react-native-toast-message";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
|
||||
@@ -94,7 +94,7 @@ export default function AddMemberProject() {
|
||||
|
||||
|
||||
return (
|
||||
<SafeAreaView>
|
||||
<>
|
||||
<Stack.Screen
|
||||
options={{
|
||||
headerLeft: () => <ButtonBackHeader onPress={() => { router.back() }} />,
|
||||
@@ -111,13 +111,13 @@ export default function AddMemberProject() {
|
||||
)
|
||||
}}
|
||||
/>
|
||||
<View style={[Styles.p15, Styles.mb100]}>
|
||||
<View style={[Styles.p15, { flex: 1 }]}>
|
||||
<InputSearch onChange={(val) => handleSearch(val)} value={search} />
|
||||
{
|
||||
selectMember.length > 0
|
||||
?
|
||||
<View>
|
||||
<ScrollView horizontal style={[Styles.mb10, Styles.pv10]}>
|
||||
<ScrollView horizontal style={[Styles.mb10, Styles.pv10]} showsHorizontalScrollIndicator={false}>
|
||||
{
|
||||
selectMember.map((item: any, index: any) => (
|
||||
<ImageWithLabel
|
||||
@@ -134,40 +134,48 @@ export default function AddMemberProject() {
|
||||
:
|
||||
<Text style={[Styles.textDefault, Styles.cGray, Styles.pv05, { textAlign: 'center' }]}>Tidak ada member yang dipilih</Text>
|
||||
}
|
||||
<ScrollView>
|
||||
<ScrollView
|
||||
showsVerticalScrollIndicator={false}
|
||||
style={[Styles.h100]}
|
||||
>
|
||||
{
|
||||
data.length > 0 ?
|
||||
data.map((item: any, index: any) => {
|
||||
const found = dataOld.some((i: any) => i.idUser == item.id)
|
||||
return (
|
||||
<Pressable
|
||||
key={index}
|
||||
style={[Styles.itemSelectModal]}
|
||||
onPress={() => {
|
||||
!found && onChoose(item.id, item.name, item.img)
|
||||
}}
|
||||
>
|
||||
<View style={[Styles.rowItemsCenter, Styles.w80]}>
|
||||
<ImageUser src={`${ConstEnv.url_storage}/files/${item.img}`} border />
|
||||
<View style={[Styles.ml10]}>
|
||||
<Text style={[Styles.textDefault]} numberOfLines={1}>{item.name}</Text>
|
||||
<View style={[Styles.mb100]}>
|
||||
{
|
||||
data.map((item: any, index: any) => {
|
||||
const found = dataOld.some((i: any) => i.idUser == item.id)
|
||||
return (
|
||||
<Pressable
|
||||
key={index}
|
||||
style={[Styles.itemSelectModal]}
|
||||
onPress={() => {
|
||||
!found && onChoose(item.id, item.name, item.img)
|
||||
}}
|
||||
>
|
||||
<View style={[Styles.rowItemsCenter, Styles.w80]}>
|
||||
<ImageUser src={`${ConstEnv.url_storage}/files/${item.img}`} border />
|
||||
<View style={[Styles.ml10]}>
|
||||
<Text style={[Styles.textDefault]} numberOfLines={1}>{item.name}</Text>
|
||||
{
|
||||
found && <Text style={[Styles.textInformation, Styles.cGray]}>sudah menjadi anggota</Text>
|
||||
}
|
||||
</View>
|
||||
</View>
|
||||
{
|
||||
found && <Text style={[Styles.textInformation, Styles.cGray]}>sudah menjadi anggota</Text>
|
||||
selectMember.some((i: any) => i.idUser == item.id) && <AntDesign name="check" size={20} color={'black'} />
|
||||
}
|
||||
</View>
|
||||
</View>
|
||||
{
|
||||
selectMember.some((i: any) => i.idUser == item.id) && <AntDesign name="check" size={20} color={'black'} />
|
||||
}
|
||||
</Pressable>
|
||||
)
|
||||
}
|
||||
)
|
||||
</Pressable>
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
</View>
|
||||
|
||||
:
|
||||
<Text style={[Styles.textDefault, { textAlign: 'center' }]}>Tidak ada data</Text>
|
||||
}
|
||||
</ScrollView>
|
||||
</View>
|
||||
</SafeAreaView>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -88,8 +88,11 @@ export default function ProjectCancel() {
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<ScrollView>
|
||||
<View style={[Styles.p15, Styles.mb100]}>
|
||||
<ScrollView
|
||||
showsVerticalScrollIndicator={false}
|
||||
style={[Styles.h100]}
|
||||
>
|
||||
<View style={[Styles.p15]}>
|
||||
<InputForm
|
||||
label="Alasan Pembatalan"
|
||||
type="default"
|
||||
@@ -99,6 +102,7 @@ export default function ProjectCancel() {
|
||||
error={error}
|
||||
errorText="Alasan pembatalan harus diisi"
|
||||
onChange={(val) => onValidation(val)}
|
||||
multiline
|
||||
/>
|
||||
</View>
|
||||
</ScrollView>
|
||||
|
||||
@@ -107,8 +107,11 @@ export default function ReportProject() {
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<ScrollView>
|
||||
<View style={[Styles.p15, Styles.mb100]}>
|
||||
<ScrollView
|
||||
showsVerticalScrollIndicator={false}
|
||||
style={[Styles.h100]}
|
||||
>
|
||||
<View style={[Styles.p15]}>
|
||||
<InputForm
|
||||
label="Laporan Kegiatan"
|
||||
type="default"
|
||||
|
||||
@@ -42,6 +42,7 @@ 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)
|
||||
@@ -86,7 +87,7 @@ export default function CreateProject() {
|
||||
} else {
|
||||
setError(error => ({ ...error, title: false }))
|
||||
}
|
||||
} else if (cat == "task" && hitung > 1) {
|
||||
} else if (cat == "task" && hitung > 2) {
|
||||
if (taskCreate.length == 0) {
|
||||
setError(error => ({ ...error, task: true }))
|
||||
} else {
|
||||
@@ -114,6 +115,13 @@ export default function CreateProject() {
|
||||
validationForm('task', '');
|
||||
}, [taskCreate]);
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
if (entityUser.role != "supadmin" && entityUser.role != "developer") {
|
||||
validationForm('group', userLogin.idGroup, userLogin.group);
|
||||
}
|
||||
}, []);
|
||||
|
||||
async function handleCreate() {
|
||||
try {
|
||||
setLoading(true)
|
||||
@@ -205,8 +213,11 @@ export default function CreateProject() {
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<ScrollView>
|
||||
<View style={[Styles.p15, Styles.mb100]}>
|
||||
<ScrollView
|
||||
showsVerticalScrollIndicator={false}
|
||||
style={[Styles.h100]}
|
||||
>
|
||||
<View style={[Styles.p15]}>
|
||||
{
|
||||
(entityUser.role == "supadmin" || entityUser.role == "developer")
|
||||
&&
|
||||
|
||||
@@ -71,7 +71,7 @@ export default function AddMemberCreateProject() {
|
||||
|
||||
|
||||
return (
|
||||
<SafeAreaView>
|
||||
<>
|
||||
<Stack.Screen
|
||||
options={{
|
||||
headerLeft: () => <ButtonBackHeader onPress={() => { router.back() }} />,
|
||||
@@ -88,14 +88,14 @@ export default function AddMemberCreateProject() {
|
||||
)
|
||||
}}
|
||||
/>
|
||||
<View style={[Styles.p15]}>
|
||||
<View style={[Styles.p15, { flex: 1 }]}>
|
||||
<InputSearch onChange={(val) => setSearch(val)} value={search} />
|
||||
|
||||
{
|
||||
selectMember.length > 0
|
||||
?
|
||||
<View>
|
||||
<ScrollView horizontal style={[Styles.mb10, Styles.pv10]}>
|
||||
<ScrollView horizontal style={[Styles.mb10, Styles.pv10]} showsHorizontalScrollIndicator={false}>
|
||||
{
|
||||
selectMember.map((item: any, index: any) => (
|
||||
<ImageWithLabel
|
||||
@@ -112,7 +112,9 @@ export default function AddMemberCreateProject() {
|
||||
:
|
||||
<Text style={[Styles.textDefault, Styles.cGray, Styles.pv05, { textAlign: 'center' }]}>Tidak ada member yang dipilih</Text>
|
||||
}
|
||||
<ScrollView>
|
||||
<ScrollView
|
||||
showsVerticalScrollIndicator={false}
|
||||
>
|
||||
|
||||
{
|
||||
data.length > 0 ?
|
||||
@@ -143,6 +145,6 @@ export default function AddMemberCreateProject() {
|
||||
}
|
||||
</ScrollView>
|
||||
</View>
|
||||
</SafeAreaView>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -99,14 +99,19 @@ export default function CreateProjectAddTask() {
|
||||
timeStart: item.timeStart,
|
||||
timeEnd: item.timeEnd,
|
||||
}))
|
||||
dispatch(setTaskCreate([...taskCreate, {
|
||||
|
||||
const hasilOrder = [...taskCreate, {
|
||||
title: title,
|
||||
dateStart: from,
|
||||
dateEnd: to,
|
||||
dateStartFix: formatDateOnly(range.startDate, "YYYY-MM-DD"),
|
||||
dateEndFix: formatDateOnly(range.endDate, "YYYY-MM-DD"),
|
||||
dataDetail: dataDetailFix
|
||||
}]))
|
||||
}].sort((a, b) => {
|
||||
return new Date(a.dateStartFix).getTime() - new Date(b.dateStartFix).getTime();
|
||||
});
|
||||
|
||||
dispatch(setTaskCreate(hasilOrder))
|
||||
router.back();
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
|
||||
@@ -120,7 +120,7 @@ export default function ListProject() {
|
||||
return (
|
||||
<View style={[Styles.p15, { flex: 1 }]}>
|
||||
<View>
|
||||
<ScrollView horizontal style={[Styles.mb10]}>
|
||||
<ScrollView horizontal style={[Styles.mb10]} showsHorizontalScrollIndicator={false}>
|
||||
<ButtonTab
|
||||
active={statusFix}
|
||||
value="0"
|
||||
@@ -193,16 +193,19 @@ export default function ListProject() {
|
||||
</Pressable>
|
||||
</View>
|
||||
<View style={[Styles.mv05]}>
|
||||
<Text>Filter :
|
||||
{
|
||||
(entityUser.role == "supadmin" || entityUser.role == "developer") && nameGroup
|
||||
}
|
||||
{
|
||||
(entityUser.role == 'user' || entityUser.role == 'coadmin' || entityUser.role == 'cosupadmin')
|
||||
? (cat == 'null' || cat == 'undefined' || cat == undefined || cat == '' || cat == 'data-saya') ? 'Kegiatan Saya' : 'Semua Kegiatan'
|
||||
: ''
|
||||
}
|
||||
</Text>
|
||||
{
|
||||
entityUser.role != 'cosupadmin' && entityUser.role != 'admin' &&
|
||||
<Text>Filter :
|
||||
{
|
||||
(entityUser.role == "supadmin" || entityUser.role == "developer") && nameGroup
|
||||
}
|
||||
{
|
||||
(entityUser.role == 'user' || entityUser.role == 'coadmin')
|
||||
? (cat == 'null' || cat == 'undefined' || cat == undefined || cat == '' || cat == 'data-saya') ? 'Kegiatan Saya' : 'Semua Kegiatan'
|
||||
: ''
|
||||
}
|
||||
</Text>
|
||||
}
|
||||
</View>
|
||||
</View>
|
||||
<View style={[{ flex: 2 }]}>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import BorderBottomItem from "@/components/borderBottomItem";
|
||||
import ButtonBackHeader from "@/components/buttonBackHeader";
|
||||
import ImageUser from "@/components/imageNew";
|
||||
import InputSearch from "@/components/inputSearch";
|
||||
import Text from '@/components/Text';
|
||||
import { ColorsStatus } from "@/constants/ColorsStatus";
|
||||
@@ -9,8 +10,8 @@ import { apiGetSearch } from "@/lib/api";
|
||||
import { useAuthSession } from "@/providers/AuthProvider";
|
||||
import { AntDesign, MaterialIcons } from "@expo/vector-icons";
|
||||
import { router, Stack } from "expo-router";
|
||||
import { useState } from "react";
|
||||
import { FlatList, Image, SafeAreaView, View } from "react-native";
|
||||
import React, { useState } from "react";
|
||||
import { RefreshControl, SafeAreaView, ScrollView, View } from "react-native";
|
||||
import Toast from "react-native-toast-message";
|
||||
|
||||
type PropsUser = {
|
||||
@@ -40,10 +41,13 @@ export default function Search() {
|
||||
const [dataUser, setDataUser] = useState<PropsUser[]>([])
|
||||
const [dataDivisi, setDataDivisi] = useState<PropDivisi[]>([])
|
||||
const [dataProject, setDataProject] = useState<PropProject[]>([])
|
||||
const [refreshing, setRefreshing] = useState(false)
|
||||
const [search, setSearch] = useState('')
|
||||
|
||||
async function handleSearch(cari: string) {
|
||||
try {
|
||||
if (cari.length > 3) {
|
||||
setSearch(cari)
|
||||
if (cari.length >= 3) {
|
||||
const user = await decryptToken(String(token?.current))
|
||||
const hasil = await apiGetSearch({ text: cari, user: user })
|
||||
if (hasil.success) {
|
||||
@@ -65,6 +69,14 @@ export default function Search() {
|
||||
}
|
||||
|
||||
|
||||
const handleRefresh = async () => {
|
||||
setRefreshing(true)
|
||||
handleSearch(search)
|
||||
await new Promise(resolve => setTimeout(resolve, 2000));
|
||||
setRefreshing(false)
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
<SafeAreaView>
|
||||
@@ -80,41 +92,44 @@ export default function Search() {
|
||||
{
|
||||
dataProject.length + dataDivisi.length + dataUser.length > 0
|
||||
?
|
||||
<View style={[Styles.wrapPaper, Styles.mb100]}>
|
||||
<ScrollView
|
||||
style={[Styles.h100]}
|
||||
refreshControl={
|
||||
<RefreshControl
|
||||
refreshing={refreshing}
|
||||
onRefresh={handleRefresh}
|
||||
/>
|
||||
}
|
||||
>
|
||||
{
|
||||
dataUser.length > 0 &&
|
||||
<View style={[Styles.mb05]}>
|
||||
<View style={[Styles.mv05, Styles.p10]}>
|
||||
<Text>ANGGOTA</Text>
|
||||
<FlatList
|
||||
data={dataUser}
|
||||
keyExtractor={(item) => String(item.id)}
|
||||
renderItem={({ item }) => (
|
||||
{
|
||||
dataUser.map((item, index) => (
|
||||
<BorderBottomItem
|
||||
key={index}
|
||||
borderType="bottom"
|
||||
icon={<Image
|
||||
source={{ uri: `${ConstEnv.url_storage}/files/${item.img}` }}
|
||||
style={[Styles.userProfileSmall]}
|
||||
/>}
|
||||
icon={<ImageUser src={`${ConstEnv.url_storage}/files/${item.img}`} />}
|
||||
title={item.name}
|
||||
subtitle={`${item.group}-${item.position}`}
|
||||
onPress={() => {
|
||||
router.push(`/member/${item.id}`)
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
))
|
||||
}
|
||||
</View>
|
||||
}
|
||||
|
||||
{
|
||||
dataDivisi.length > 0 &&
|
||||
<View style={[Styles.mb05]}>
|
||||
<View style={[Styles.mv05, Styles.p10]}>
|
||||
<Text>DIVISI</Text>
|
||||
<FlatList
|
||||
data={dataDivisi}
|
||||
keyExtractor={(item) => String(item.id)}
|
||||
renderItem={({ item }) => (
|
||||
{
|
||||
dataDivisi.map((item, index) => (
|
||||
<BorderBottomItem
|
||||
key={index}
|
||||
borderType="bottom"
|
||||
icon={
|
||||
<View style={[Styles.iconContent, ColorsStatus.primary]}>
|
||||
@@ -127,21 +142,20 @@ export default function Search() {
|
||||
router.push(`/division/${item.id}`)
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
))
|
||||
}
|
||||
</View>
|
||||
}
|
||||
|
||||
|
||||
{
|
||||
dataProject.length > 0 &&
|
||||
<View style={[Styles.mb10]}>
|
||||
<View style={[Styles.mv05, Styles.p10]}>
|
||||
<Text>KEGIATAN</Text>
|
||||
<FlatList
|
||||
data={dataProject}
|
||||
keyExtractor={(item) => String(item.id)}
|
||||
renderItem={({ item }) => (
|
||||
{
|
||||
dataProject.map((item, index) => (
|
||||
<BorderBottomItem
|
||||
key={index}
|
||||
borderType="bottom"
|
||||
icon={
|
||||
<View style={[Styles.iconContent, ColorsStatus.primary]}>
|
||||
@@ -154,13 +168,11 @@ export default function Search() {
|
||||
router.push(`/project/${item.id}`)
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
))
|
||||
}
|
||||
</View>
|
||||
}
|
||||
|
||||
|
||||
</View>
|
||||
</ScrollView>
|
||||
:
|
||||
<View style={[Styles.contentItemCenter, Styles.mt10]}>
|
||||
<Text style={[Styles.textInformation, Styles.cGray]}>Tidak ada data</Text>
|
||||
|
||||
@@ -27,10 +27,12 @@ export default function Index() {
|
||||
<View style={Styles.wrapLogin} >
|
||||
<View style={{ alignItems: "center", marginVertical: 50 }}>
|
||||
<Image
|
||||
source={require("../assets/images/splash-icon.png")}
|
||||
style={{ width: 130, height: 130 }}
|
||||
source={require("../assets/images/logo.png")}
|
||||
style={[{ width: 300, height: 150 }]}
|
||||
width={270}
|
||||
height={110}
|
||||
/>
|
||||
<Text style={[Styles.textSubtitle]}>PERBEKEL DARMASABA</Text>
|
||||
{/* <Text style={[Styles.textSubtitle]}>PERBEKEL DARMASABA</Text> */}
|
||||
</View>
|
||||
<View style={[Styles.mb30]}>
|
||||
<Text style={[Styles.textDefaultSemiBold]}>Verifikasi Nomor Telepon</Text>
|
||||
|
||||
BIN
assets/images/logo-clean.png
Normal file
|
After Width: | Height: | Size: 63 KiB |
BIN
assets/images/logo-icon-small.png
Normal file
|
After Width: | Height: | Size: 27 KiB |
BIN
assets/images/logo-icon.png
Normal file
|
After Width: | Height: | Size: 9.7 KiB |
BIN
assets/images/logo.png
Normal file
|
After Width: | Height: | Size: 54 KiB |
38
bump-version.js
Normal file
@@ -0,0 +1,38 @@
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
|
||||
const configPath = path.join(__dirname, "app.config.js");
|
||||
let configFile = fs.readFileSync(configPath, "utf8");
|
||||
|
||||
// --- Update versionCode ---
|
||||
const codeRegex = /versionCode:\s*(\d+)/;
|
||||
const codeMatch = configFile.match(codeRegex);
|
||||
|
||||
if (!codeMatch) {
|
||||
console.error("❌ Tidak menemukan versionCode di app.config.js");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const currentCode = parseInt(codeMatch[1], 10);
|
||||
const newCode = currentCode + 1;
|
||||
configFile = configFile.replace(codeRegex, `versionCode: ${newCode}`);
|
||||
|
||||
// --- Update versionName ---
|
||||
const nameRegex = /version:\s*"(.*?)"/;
|
||||
const nameMatch = configFile.match(nameRegex);
|
||||
|
||||
if (!nameMatch) {
|
||||
console.error("❌ Tidak menemukan version di app.config.js");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
let [major, minor, patch] = nameMatch[1].split(".").map(Number);
|
||||
patch += 1; // bump patch version
|
||||
const newName = `${major}.${minor}.${patch}`;
|
||||
configFile = configFile.replace(nameRegex, `version: "${newName}"`);
|
||||
|
||||
// --- Simpan file ---
|
||||
fs.writeFileSync(configPath, configFile, "utf8");
|
||||
|
||||
console.log(`✅ versionCode: ${currentCode} → ${newCode}`);
|
||||
console.log(`✅ versionName: ${nameMatch[1]} → ${newName}`);
|
||||
@@ -1,8 +1,10 @@
|
||||
import Styles from "@/constants/Styles"
|
||||
import { apiCheckPhoneLogin, apiSendOtp } from "@/lib/api"
|
||||
import { useAuthSession } from "@/providers/AuthProvider"
|
||||
import AsyncStorage from "@react-native-async-storage/async-storage"
|
||||
import { StatusBar } from "expo-status-bar"
|
||||
import { useState } from "react"
|
||||
import { Image, SafeAreaView, View } from "react-native"
|
||||
import { Image, Platform, SafeAreaView, View } from "react-native"
|
||||
import Toast from "react-native-toast-message"
|
||||
import { ButtonForm } from "../buttonForm"
|
||||
import { InputForm } from "../inputForm"
|
||||
@@ -10,7 +12,6 @@ import ModalLoading from "../modalLoading"
|
||||
import Text from "../Text"
|
||||
import ToastCustom from "../toastCustom"
|
||||
|
||||
|
||||
type Props = {
|
||||
onValidate: ({ phone, otp }: { phone: string, otp: number }) => void
|
||||
}
|
||||
@@ -19,22 +20,29 @@ export default function ViewLogin({ onValidate }: Props) {
|
||||
const [loadingLogin, setLoadingLogin] = useState(false)
|
||||
const [disableLogin, setDisableLogin] = useState(true)
|
||||
const [phone, setPhone] = useState('')
|
||||
const { signIn, encryptToken } = useAuthSession();
|
||||
|
||||
const handleCheckPhone = async () => {
|
||||
try {
|
||||
setLoadingLogin(true)
|
||||
const response = await apiCheckPhoneLogin({ phone: `62${phone}` });
|
||||
const response = await apiCheckPhoneLogin({ phone: `62${phone}` })
|
||||
if (response.success) {
|
||||
const otp = Math.floor(1000 + Math.random() * 9000)
|
||||
const responseOtp = await apiSendOtp({ phone: `62${phone}`, otp })
|
||||
if (responseOtp == 200) {
|
||||
await AsyncStorage.setItem('user', response.id);
|
||||
return onValidate({ phone: `62${phone}`, otp })
|
||||
if (response.isWithoutOTP) {
|
||||
const encrypted = await encryptToken(response.id)
|
||||
signIn(encrypted)
|
||||
} else {
|
||||
const otp = Math.floor(1000 + Math.random() * 9000)
|
||||
const responseOtp = await apiSendOtp({ phone: `62${phone}`, otp })
|
||||
if (responseOtp == 200) {
|
||||
await AsyncStorage.setItem('user', response.id)
|
||||
return onValidate({ phone: `62${phone}`, otp })
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return Toast.show({ type: 'small', text1: response.message, position: 'top' })
|
||||
}
|
||||
return Toast.show({ type: 'small', text1: response.message, position: 'top' })
|
||||
} catch (error) {
|
||||
return Toast.show({ type: 'small', text1: 'Terjadi kesalahan', position: 'top' })
|
||||
return Toast.show({ type: 'small', text1: `Terjadi kesalahan, coba lagi`, position: 'top' })
|
||||
} finally {
|
||||
setLoadingLogin(false)
|
||||
}
|
||||
@@ -42,14 +50,17 @@ export default function ViewLogin({ onValidate }: Props) {
|
||||
|
||||
return (
|
||||
<SafeAreaView>
|
||||
<StatusBar style={Platform.OS === 'ios' ? 'dark' : 'light'} translucent={false} backgroundColor="black" />
|
||||
<ToastCustom />
|
||||
<View style={[Styles.p20, Styles.h100]}>
|
||||
<View style={{ alignItems: "center", marginVertical: 50 }}>
|
||||
<View style={{ alignItems: "center", marginTop: 70, marginBottom: 50 }}>
|
||||
<Image
|
||||
source={require("../../assets/images/splash-icon.png")}
|
||||
style={{ width: 130, height: 130 }}
|
||||
source={require("../../assets/images/logo.png")}
|
||||
style={[{ width: 300, height: 150 }]}
|
||||
width={270}
|
||||
height={110}
|
||||
/>
|
||||
<Text style={[Styles.textSubtitle]}>PERBEKEL DARMASABA</Text>
|
||||
{/* <Text style={[Styles.textSubtitle2]}>Digitalisasi Desa Transparansi Kerja</Text> */}
|
||||
</View>
|
||||
<InputForm
|
||||
onChange={(val) => {
|
||||
|
||||
@@ -2,8 +2,9 @@ import Styles from "@/constants/Styles";
|
||||
import { apiSendOtp } from "@/lib/api";
|
||||
import { useAuthSession } from "@/providers/AuthProvider";
|
||||
import AsyncStorage from "@react-native-async-storage/async-storage";
|
||||
import { StatusBar } from "expo-status-bar";
|
||||
import { useState } from "react";
|
||||
import { Image, View } from "react-native";
|
||||
import { Image, Platform, View } from "react-native";
|
||||
import { OtpInput } from "react-native-otp-entry";
|
||||
import Toast from 'react-native-toast-message';
|
||||
import { ButtonForm } from "../buttonForm";
|
||||
@@ -56,14 +57,17 @@ export default function ViewVerification({ phone, otp }: Props) {
|
||||
|
||||
return (
|
||||
<>
|
||||
<StatusBar style={Platform.OS === 'ios' ? 'dark' : 'light'} translucent={false} backgroundColor="black" />
|
||||
<ToastCustom />
|
||||
<View style={Styles.wrapLogin} >
|
||||
<View style={{ alignItems: "center", marginVertical: 50 }}>
|
||||
<View style={{ alignItems: "center", marginTop: 70, marginBottom: 50 }}>
|
||||
<Image
|
||||
source={require("../../assets/images/splash-icon.png")}
|
||||
style={{ width: 130, height: 130 }}
|
||||
source={require("../../assets/images/logo.png")}
|
||||
style={[{ width: 300, height: 150 }]}
|
||||
width={270}
|
||||
height={110}
|
||||
/>
|
||||
<Text style={[Styles.textSubtitle]}>PERBEKEL DARMASABA</Text>
|
||||
{/* <Text style={[Styles.textSubtitle]}>PERBEKEL DARMASABA</Text> */}
|
||||
</View>
|
||||
<View style={[Styles.mb30]}>
|
||||
<Text style={[Styles.textDefaultSemiBold]}>Verifikasi Nomor Telepon</Text>
|
||||
|
||||
@@ -11,6 +11,7 @@ type Props = {
|
||||
desc?: string
|
||||
rightTopInfo?: string
|
||||
onPress?: () => void
|
||||
onLongPress?: () => void
|
||||
borderType: 'all' | 'bottom' | 'none'
|
||||
leftBottomInfo?: React.ReactNode | string
|
||||
rightBottomInfo?: React.ReactNode | string
|
||||
@@ -21,13 +22,13 @@ type Props = {
|
||||
textColor?: string
|
||||
}
|
||||
|
||||
export default function BorderBottomItem({ title, subtitle, icon, desc, onPress, rightTopInfo, borderType, leftBottomInfo, rightBottomInfo, titleWeight, bgColor, width, descEllipsize, textColor }: Props) {
|
||||
export default function BorderBottomItem({ title, subtitle, icon, desc, onPress, onLongPress, rightTopInfo, borderType, leftBottomInfo, rightBottomInfo, titleWeight, bgColor, width, descEllipsize, textColor }: Props) {
|
||||
const lebarDim = Dimensions.get("window").width;
|
||||
const lebar = width ? lebarDim * width / 100 : 'auto';
|
||||
const textColorFix = textColor ? textColor : 'black';
|
||||
|
||||
return (
|
||||
<Pressable style={[borderType == 'bottom' ? Styles.wrapItemBorderBottom : borderType == 'all' ? Styles.wrapItemBorderAll : Styles.wrapItemBorderNone, bgColor && bgColor == 'white' && ColorsStatus.white]} onPress={onPress}>
|
||||
<Pressable onLongPress={onLongPress} style={[borderType == 'bottom' ? Styles.wrapItemBorderBottom : borderType == 'all' ? Styles.wrapItemBorderAll : Styles.wrapItemBorderNone, bgColor && bgColor == 'white' && ColorsStatus.white]} onPress={onPress}>
|
||||
<View style={[Styles.rowItemsCenter]}>
|
||||
{icon}
|
||||
<View style={[Styles.rowSpaceBetween, width ? { width: lebar } : { width: '88%' }]}>
|
||||
@@ -48,20 +49,20 @@ export default function BorderBottomItem({ title, subtitle, icon, desc, onPress,
|
||||
</View>
|
||||
|
||||
</View>
|
||||
{desc && <Text style={[Styles.textDefault, Styles.mt05, { textAlign: 'justify', color: textColorFix }]} numberOfLines={descEllipsize == false ? 0 : 2} ellipsizeMode='tail'>{desc}</Text>}
|
||||
{desc && <Text style={[Styles.textDefault, Styles.mt05, { textAlign: 'left', color: textColorFix }]} numberOfLines={descEllipsize == false ? 0 : 2} ellipsizeMode='tail'>{desc}</Text>}
|
||||
{
|
||||
(leftBottomInfo || rightBottomInfo) &&
|
||||
(
|
||||
<View style={[Styles.rowSpaceBetween, Styles.mt10]}>
|
||||
<View style={[rightBottomInfo && !leftBottomInfo ? Styles.rowSpaceBetweenReverse : Styles.rowSpaceBetween, Styles.mt05]}>
|
||||
{
|
||||
typeof leftBottomInfo == 'string' ?
|
||||
<Text style={[Styles.textInformation, Styles.cGray, { textAlign: 'left' }]}>{leftBottomInfo}</Text>
|
||||
<Text style={[Styles.textInformation, Styles.cGray]}>{leftBottomInfo}</Text>
|
||||
:
|
||||
leftBottomInfo
|
||||
}
|
||||
{
|
||||
typeof rightBottomInfo == 'string' ?
|
||||
<Text style={[Styles.textInformation, Styles.mt05, Styles.cGray, { textAlign: 'right' }]}>{rightBottomInfo}</Text>
|
||||
<Text style={[Styles.textInformation, Styles.cGray]}>{rightBottomInfo}</Text>
|
||||
:
|
||||
rightBottomInfo
|
||||
}
|
||||
|
||||
@@ -15,7 +15,9 @@ export default function DiscussionItem({ title, user, date, onPress }: Props) {
|
||||
<Pressable style={[Styles.wrapItemDiscussion]} onPress={onPress}>
|
||||
<View style={[Styles.rowItemsCenter, Styles.mb10]}>
|
||||
<Ionicons name="chatbox-ellipses-outline" size={22} color="black" style={Styles.mr10} />
|
||||
<Text style={{ fontWeight: 'bold' }} numberOfLines={1} ellipsizeMode="tail">{title}</Text>
|
||||
<View style={[{flex:1}]}>
|
||||
<Text style={{ fontWeight: 'bold' }} numberOfLines={1} ellipsizeMode="tail">{title}</Text>
|
||||
</View>
|
||||
</View>
|
||||
<View style={Styles.rowSpaceBetween}>
|
||||
<View style={Styles.rowItemsCenter}>
|
||||
|
||||
@@ -16,7 +16,10 @@ export default function HeaderDiscussionGeneral() {
|
||||
|
||||
return (
|
||||
<>
|
||||
<ButtonMenuHeader onPress={() => { setVisible(true) }} />
|
||||
{
|
||||
entityUser.role != "user" && entityUser.role != "coadmin" &&
|
||||
<ButtonMenuHeader onPress={() => { setVisible(true) }} />
|
||||
}
|
||||
<DrawerBottom animation="slide" isVisible={isVisible} setVisible={setVisible} title="Menu">
|
||||
<View style={Styles.rowItemsCenter}>
|
||||
<MenuItemRow
|
||||
|
||||
@@ -16,15 +16,15 @@ type Props = {
|
||||
date: string;
|
||||
};
|
||||
|
||||
export default function DiscussionDivisionDetail() {
|
||||
export default function DiscussionDivisionDetail({ refreshing }: { refreshing: boolean }) {
|
||||
const { token, decryptToken } = useAuthSession();
|
||||
const { id } = useLocalSearchParams<{ id: string }>();
|
||||
const [data, setData] = useState<Props[]>([]);
|
||||
const [loading, setLoading] = useState(true)
|
||||
|
||||
async function handleLoad() {
|
||||
async function handleLoad(loading: boolean) {
|
||||
try {
|
||||
setLoading(true)
|
||||
setLoading(loading)
|
||||
const hasil = await decryptToken(String(token?.current));
|
||||
const response = await apiGetDivisionOneFeature({
|
||||
user: hasil,
|
||||
@@ -40,8 +40,15 @@ export default function DiscussionDivisionDetail() {
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
handleLoad();
|
||||
}, []);
|
||||
if (refreshing)
|
||||
handleLoad(false)
|
||||
}, [refreshing])
|
||||
|
||||
useEffect(() => {
|
||||
handleLoad(true)
|
||||
}, [])
|
||||
|
||||
|
||||
return (
|
||||
<View style={[Styles.mb15]}>
|
||||
<Text style={[Styles.textDefaultSemiBold, Styles.mv10]}>Diskusi</Text>
|
||||
|
||||
@@ -24,7 +24,7 @@ type Props = {
|
||||
idStorage: string
|
||||
}
|
||||
|
||||
export default function FileDivisionDetail() {
|
||||
export default function FileDivisionDetail({ refreshing }: { refreshing: boolean }) {
|
||||
const ref = React.useRef<ICarouselInstance>(null);
|
||||
const width = Dimensions.get("window").width;
|
||||
const [data, setData] = useState<Props[]>([])
|
||||
@@ -33,9 +33,9 @@ export default function FileDivisionDetail() {
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [loadingOpen, setLoadingOpen] = useState(false)
|
||||
|
||||
async function handleLoad() {
|
||||
async function handleLoad(loading: boolean) {
|
||||
try {
|
||||
setLoading(true)
|
||||
setLoading(loading)
|
||||
const hasil = await decryptToken(String(token?.current))
|
||||
const response = await apiGetDivisionOneFeature({ user: hasil, id, cat: 'new-file' })
|
||||
setData(response.data)
|
||||
@@ -47,7 +47,12 @@ export default function FileDivisionDetail() {
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
handleLoad()
|
||||
if (refreshing)
|
||||
handleLoad(false)
|
||||
}, [refreshing])
|
||||
|
||||
useEffect(() => {
|
||||
handleLoad(true)
|
||||
}, [])
|
||||
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ type Props = {
|
||||
kalender: number
|
||||
}
|
||||
|
||||
export default function FiturDivisionDetail() {
|
||||
export default function FiturDivisionDetail({ refreshing }: { refreshing: boolean }) {
|
||||
const { token, decryptToken } = useAuthSession()
|
||||
const { id } = useLocalSearchParams<{ id: string }>()
|
||||
const [data, setData] = useState<Props>({
|
||||
@@ -36,7 +36,10 @@ export default function FiturDivisionDetail() {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
if (refreshing)
|
||||
handleLoad()
|
||||
}, [refreshing])
|
||||
|
||||
useEffect(() => {
|
||||
handleLoad()
|
||||
|
||||
@@ -18,7 +18,7 @@ type Props = {
|
||||
projectTitle: string
|
||||
}
|
||||
|
||||
export default function TaskDivisionDetail() {
|
||||
export default function TaskDivisionDetail({ refreshing }: { refreshing: boolean }) {
|
||||
const { token, decryptToken } = useAuthSession()
|
||||
const { id } = useLocalSearchParams<{ id: string }>()
|
||||
const [data, setData] = useState<Props[]>([])
|
||||
@@ -26,9 +26,9 @@ export default function TaskDivisionDetail() {
|
||||
const width = Dimensions.get("window").width;
|
||||
const [loading, setLoading] = useState(true)
|
||||
|
||||
async function handleLoad() {
|
||||
async function handleLoad(loading: boolean) {
|
||||
try {
|
||||
setLoading(true)
|
||||
setLoading(loading)
|
||||
const hasil = await decryptToken(String(token?.current))
|
||||
const response = await apiGetDivisionOneFeature({ user: hasil, id, cat: 'today-task' })
|
||||
setData(response.data)
|
||||
@@ -40,7 +40,12 @@ export default function TaskDivisionDetail() {
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
handleLoad()
|
||||
if (refreshing)
|
||||
handleLoad(false)
|
||||
}, [refreshing])
|
||||
|
||||
useEffect(() => {
|
||||
handleLoad(true)
|
||||
}, [])
|
||||
|
||||
return (
|
||||
|
||||
@@ -15,7 +15,7 @@ import { InputForm } from "../inputForm";
|
||||
import MenuItemRow from "../menuItemRow";
|
||||
import ModalFloat from "../modalFloat";
|
||||
|
||||
export default function HeaderRightDocument({ path }: { path: string }) {
|
||||
export default function HeaderRightDocument({ path, isMember }: { path: string, isMember: boolean }) {
|
||||
const [isVisible, setVisible] = useState(false);
|
||||
const [newFolder, setNewFolder] = useState(false);
|
||||
const { id } = useLocalSearchParams<{ id: string }>();
|
||||
@@ -24,9 +24,12 @@ export default function HeaderRightDocument({ path }: { path: string }) {
|
||||
const dispatch = useDispatch()
|
||||
const update = useSelector((state: any) => state.dokumenUpdate)
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [loadingFolder, setLoadingFolder] = useState(false)
|
||||
const entityUser = useSelector((state: any) => state.user)
|
||||
|
||||
async function handleCreateFolder() {
|
||||
try {
|
||||
setLoadingFolder(true)
|
||||
const hasil = await decryptToken(String(token?.current))
|
||||
const response = await apiCreateFolderDocument({ data: { user: hasil, name, path, idDivision: id } })
|
||||
if (response.success) {
|
||||
@@ -39,6 +42,7 @@ export default function HeaderRightDocument({ path }: { path: string }) {
|
||||
console.error(error)
|
||||
Toast.show({ type: 'small', text1: 'Terjadi kesalahan', })
|
||||
} finally {
|
||||
setLoadingFolder(false)
|
||||
setNewFolder(false)
|
||||
}
|
||||
}
|
||||
@@ -99,11 +103,14 @@ export default function HeaderRightDocument({ path }: { path: string }) {
|
||||
|
||||
return (
|
||||
<>
|
||||
<ButtonMenuHeader
|
||||
onPress={() => {
|
||||
setVisible(true);
|
||||
}}
|
||||
/>
|
||||
{
|
||||
((entityUser.role != "user" && entityUser.role != "coadmin") || isMember) &&
|
||||
<ButtonMenuHeader
|
||||
onPress={() => {
|
||||
setVisible(true);
|
||||
}}
|
||||
/>
|
||||
}
|
||||
<DrawerBottom
|
||||
animation="slide"
|
||||
isVisible={isVisible}
|
||||
@@ -148,7 +155,7 @@ export default function HeaderRightDocument({ path }: { path: string }) {
|
||||
title="Buat Folder Baru"
|
||||
isVisible={newFolder}
|
||||
setVisible={() => { setNewFolder(false) }}
|
||||
disableSubmit={name == ""}
|
||||
disableSubmit={name == "" || loadingFolder}
|
||||
onSubmit={() => { handleCreateFolder() }}
|
||||
>
|
||||
<View>
|
||||
|
||||
@@ -11,9 +11,10 @@ type Props = {
|
||||
checked?: boolean
|
||||
onChecked?: () => void
|
||||
onPress?: () => void
|
||||
canChecked?: boolean
|
||||
}
|
||||
|
||||
export default function ItemFile({ category, checked, dateTime, title, onChecked, onPress }: Props) {
|
||||
export default function ItemFile({ category, checked, dateTime, title, onChecked, onPress, canChecked }: Props) {
|
||||
return (
|
||||
<View style={[Styles.wrapItemBorderBottom]}>
|
||||
<View style={[Styles.rowItemsCenter]}>
|
||||
@@ -43,18 +44,22 @@ export default function ItemFile({ category, checked, dateTime, title, onChecked
|
||||
|
||||
</Pressable>
|
||||
<View style={[Styles.rowSpaceBetween, { flex: 1, alignItems: 'center' }]}>
|
||||
<Pressable style={[Styles.ml10, {flex:1},]} onPress={onPress}>
|
||||
<Pressable style={[Styles.ml10, { flex: 1 },]} onPress={onPress}>
|
||||
<Text style={[Styles.textDefault]} numberOfLines={1} ellipsizeMode="tail">{title}</Text>
|
||||
<Text style={[Styles.textInformation]}>{dateTime}</Text>
|
||||
</Pressable>
|
||||
<Pressable onPress={onChecked}>
|
||||
{
|
||||
checked
|
||||
? <MaterialCommunityIcons name="checkbox-marked-circle" size={25} color={'black'} />
|
||||
: <MaterialCommunityIcons name="checkbox-blank-circle-outline" size={25} color={'#ced4da'} />
|
||||
}
|
||||
{
|
||||
!canChecked ? <></>
|
||||
:
|
||||
<Pressable onPress={onChecked}>
|
||||
{
|
||||
checked
|
||||
? <MaterialCommunityIcons name="checkbox-marked-circle" size={25} color={'black'} />
|
||||
: <MaterialCommunityIcons name="checkbox-blank-circle-outline" size={25} color={'#ced4da'} />
|
||||
}
|
||||
|
||||
</Pressable>
|
||||
</Pressable>
|
||||
}
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
65
components/document/modalNewFolder.tsx
Normal file
@@ -0,0 +1,65 @@
|
||||
import Styles from "@/constants/Styles";
|
||||
import { apiCreateFolderDocument } from "@/lib/api";
|
||||
import { useAuthSession } from "@/providers/AuthProvider";
|
||||
import { useLocalSearchParams } from "expo-router";
|
||||
import { useState } from "react";
|
||||
import { Pressable, View } from "react-native";
|
||||
import Toast from "react-native-toast-message";
|
||||
import Text from "../Text";
|
||||
import { InputForm } from "../inputForm";
|
||||
import ModalFloat from "../modalFloat";
|
||||
|
||||
export function ModalNewFolder({ path, onCreated }: { path: string, onCreated: () => void }) {
|
||||
const { token, decryptToken } = useAuthSession()
|
||||
const [newFolder, setNewFolder] = useState(false)
|
||||
const [name, setName] = useState("")
|
||||
const [loadingFolder, setLoadingFolder] = useState(false)
|
||||
const { id } = useLocalSearchParams<{ id: string }>();
|
||||
|
||||
|
||||
async function handleCreateFolder() {
|
||||
try {
|
||||
setLoadingFolder(true)
|
||||
const hasil = await decryptToken(String(token?.current))
|
||||
const response = await apiCreateFolderDocument({ data: { user: hasil, name, path, idDivision: id } })
|
||||
if (response.success) {
|
||||
Toast.show({ type: 'small', text1: 'Berhasil membuat folder baru', })
|
||||
} else {
|
||||
Toast.show({ type: 'small', text1: response.message, })
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
Toast.show({ type: 'small', text1: 'Terjadi kesalahan', })
|
||||
} finally {
|
||||
onCreated()
|
||||
setLoadingFolder(false)
|
||||
setNewFolder(false)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Pressable style={[Styles.pv05, Styles.borderRight, { width: '50%' }]} onPress={() => setNewFolder(true)}>
|
||||
<Text style={[Styles.textDefaultSemiBold, { textAlign: 'center' }]}>FOLDER BARU</Text>
|
||||
</Pressable>
|
||||
|
||||
<ModalFloat
|
||||
title="Buat Folder Baru"
|
||||
isVisible={newFolder}
|
||||
setVisible={() => { setNewFolder(false) }}
|
||||
disableSubmit={name == "" || loadingFolder}
|
||||
onSubmit={() => { handleCreateFolder() }}
|
||||
>
|
||||
<View>
|
||||
<InputForm
|
||||
type="default"
|
||||
placeholder="Nama Folder"
|
||||
required
|
||||
label="Nama Folder"
|
||||
onChange={(value: string) => { setName(value) }}
|
||||
/>
|
||||
</View>
|
||||
</ModalFloat>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import { Pressable, View } from "react-native"
|
||||
import BorderBottomItem from "../borderBottomItem"
|
||||
import DrawerBottom from "../drawerBottom"
|
||||
import Text from "../Text"
|
||||
import { ModalNewFolder } from "./modalNewFolder"
|
||||
|
||||
type Props = {
|
||||
open: boolean
|
||||
@@ -106,9 +107,7 @@ export default function ModalSalinMove({ open, close, category, onConfirm, dataC
|
||||
}
|
||||
</View>
|
||||
<View style={[Styles.rowOnly, Styles.mt15, Styles.absolute0]}>
|
||||
<Pressable style={[Styles.pv05, Styles.borderRight, { width: '50%' }]} onPress={() => close(false)}>
|
||||
<Text style={[Styles.textDefaultSemiBold, { textAlign: 'center' }]}>BATAL</Text>
|
||||
</Pressable>
|
||||
<ModalNewFolder path={path} onCreated={() => getData()} />
|
||||
<Pressable style={[Styles.pv05, { width: '50%' }]} onPress={() => onConfirm(path)}>
|
||||
<Text style={[Styles.textDefaultSemiBold, { textAlign: 'center' }]}>{category == 'copy' ? 'SALIN' : 'PINDAH'}</Text>
|
||||
</Pressable>
|
||||
|
||||
@@ -35,6 +35,7 @@ export default function DrawerBottom({ isVisible, setVisible, title, children, a
|
||||
backdropTransitionInTiming={500}
|
||||
backdropTransitionOutTiming={500}
|
||||
useNativeDriverForBackdrop={true}
|
||||
propagateSwipe={true}
|
||||
>
|
||||
{
|
||||
keyboard ?
|
||||
@@ -62,7 +63,7 @@ export default function DrawerBottom({ isVisible, setVisible, title, children, a
|
||||
<MaterialIcons name="close" color="black" size={22} />
|
||||
</Pressable>
|
||||
</View>
|
||||
<View style={Styles.contentContainer}>
|
||||
<View style={[Styles.contentContainer, { flex: 1 }]}>
|
||||
{children}
|
||||
</View>
|
||||
</View>
|
||||
|
||||
@@ -15,7 +15,7 @@ export default function EventItem({ category, title, user, jamAwal, jamAkhir, on
|
||||
return (
|
||||
<Pressable style={[Styles.itemEvent, { backgroundColor: category == 'orange' ? '#FED6C5' : '#D8D8F1' }]} onPress={onPress}>
|
||||
<View style={[Styles.dividerEvent, { backgroundColor: category == 'orange' ? '#FB804C' : '#535FCA' }]} />
|
||||
<View>
|
||||
<View style={[Styles.w90]}>
|
||||
<Text>{jamAwal} - {jamAkhir}</Text>
|
||||
<Text numberOfLines={1} ellipsizeMode="tail" style={[Styles.textDefaultSemiBold, Styles.mv05]}>{title}</Text>
|
||||
<Text numberOfLines={1} ellipsizeMode="tail">Dibuat oleh : {user}</Text>
|
||||
|
||||
@@ -11,7 +11,7 @@ import Carousel, { ICarouselInstance } from "react-native-reanimated-carousel";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import Text from "../Text";
|
||||
|
||||
export default function CaraouselHome() {
|
||||
export default function CaraouselHome({ refreshing }: { refreshing: boolean }) {
|
||||
const { decryptToken, token } = useAuthSession()
|
||||
const ref = React.useRef<ICarouselInstance>(null);
|
||||
const width = Dimensions.get("window").width;
|
||||
@@ -37,6 +37,11 @@ export default function CaraouselHome() {
|
||||
dispatch(setEntityUser({ role: response.data.idUserRole, admin: false }))
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (refreshing)
|
||||
handleBannerView()
|
||||
}, [refreshing]);
|
||||
|
||||
useEffect(() => {
|
||||
handleBannerView()
|
||||
}, [dispatch]);
|
||||
|
||||
@@ -13,7 +13,7 @@ type Props = {
|
||||
frontColor: string;
|
||||
}[]
|
||||
|
||||
export default function ChartDokumenHome() {
|
||||
export default function ChartDokumenHome({ refreshing }: { refreshing: boolean }) {
|
||||
const [loading, setLoading] = useState(true)
|
||||
const { decryptToken, token } = useAuthSession()
|
||||
const [data, setData] = useState<Props>([])
|
||||
@@ -25,9 +25,9 @@ export default function ChartDokumenHome() {
|
||||
const width = Dimensions.get("window").width;
|
||||
|
||||
|
||||
async function handleData() {
|
||||
async function handleData(loading: boolean) {
|
||||
try {
|
||||
setLoading(true)
|
||||
setLoading(loading)
|
||||
const hasil = await decryptToken(String(token?.current))
|
||||
const response = await apiGetDataHome({ cat: "dokumen", user: hasil })
|
||||
const maxValue = response.data.reduce((max: number, obj: { value: number; }) => Math.max(max, obj.value), -Infinity);
|
||||
@@ -47,7 +47,12 @@ export default function ChartDokumenHome() {
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
handleData()
|
||||
if (refreshing)
|
||||
handleData(false)
|
||||
}, [refreshing]);
|
||||
|
||||
useEffect(() => {
|
||||
handleData(true)
|
||||
}, []);
|
||||
|
||||
return (
|
||||
|
||||
@@ -13,14 +13,14 @@ type Props = {
|
||||
color: string;
|
||||
}[]
|
||||
|
||||
export default function ChartProgresHome() {
|
||||
export default function ChartProgresHome({ refreshing }: { refreshing: boolean }) {
|
||||
const { decryptToken, token } = useAuthSession()
|
||||
const [data, setData] = useState<Props>([])
|
||||
const [loading, setLoading] = useState(true)
|
||||
|
||||
async function handleData() {
|
||||
async function handleData(loading: boolean) {
|
||||
try {
|
||||
setLoading(true)
|
||||
setLoading(loading)
|
||||
const hasil = await decryptToken(String(token?.current))
|
||||
const response = await apiGetDataHome({ cat: "progress", user: hasil })
|
||||
const convertedArray = response.data.map((item: { color: any; text: any; value: any; }) => ({
|
||||
@@ -37,7 +37,12 @@ export default function ChartProgresHome() {
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
handleData()
|
||||
if (refreshing)
|
||||
handleData(false)
|
||||
}, [refreshing]);
|
||||
|
||||
useEffect(() => {
|
||||
handleData(true)
|
||||
}, []);
|
||||
|
||||
return (
|
||||
|
||||