Browse Source

feat(dark mode): add dark mode

master
farhantock 3 months ago
parent
commit
ffd2acf3f2
  1. 13
      src/components/SearchPage.js
  2. 93
      src/components/renderSkeleton.js
  3. 19
      src/navigation/BottomTabNavigator.js
  4. 53
      src/screens/Home.js
  5. 87
      src/screens/Login.js
  6. 154
      src/screens/Profile.js
  7. 13
      src/screens/Service.js
  8. 80
      src/screens/presence/index.js
  9. 248
      src/screens/registerPage/index.js

13
src/components/SearchPage.js

@ -1,11 +1,12 @@
import React from 'react';
import { List, Searchbar, TouchableRipple } from 'react-native-paper';
import { List, Searchbar, TouchableRipple, useTheme } from 'react-native-paper';
import { StyleSheet, View, ScrollView } from 'react-native';
import { useRoute } from '@react-navigation/native';
import { colors } from '../utils/color';
import Icon from 'react-native-vector-icons/AntDesign';
export default function SearchPage({ navigation }) {
const theme = useTheme();
const [searchQuery, setSearchQuery] = React.useState('');
const route = useRoute();
const { dataListProjectCharters, onSelect } = route.params;
@ -20,21 +21,21 @@ export default function SearchPage({ navigation }) {
);
return (
<View style={styles.container}>
<View style={[styles.container, { backgroundColor: theme.colors.background }]}>
<Searchbar
placeholder="Cari Project"
onChangeText={setSearchQuery}
value={searchQuery}
style={{ backgroundColor: colors.white }}
style={{ backgroundColor: theme.dark ? theme.colors.surface : theme.colors.pureWhite, marginTop: 50, marginHorizontal: 10 }}
/>
<ScrollView style={styles.listData}>
<ScrollView style={[styles.listData, { backgroundColor: theme.colors.background }]}>
<View>
{filteredData.map(item => (
<TouchableRipple key={item.id} onPress={() => handleProjectSelect(item)}>
<List.Item
title={item.sicn}
titleNumberOfLines={3}
left={() => <Icon name="search1" size={20} color={colors.black} />}
left={() => <Icon name="search1" size={20} color={theme.dark ? theme.colors.surface : theme.colors.black} />}
right={() => <List.Icon icon="arrow-top-left" />}
/>
</TouchableRipple>
@ -48,8 +49,6 @@ export default function SearchPage({ navigation }) {
const styles = StyleSheet.create({
container: {
flex: 1,
marginTop: 50,
marginHorizontal: 10,
},
listData: {
flex: 1,

93
src/components/renderSkeleton.js

@ -2,49 +2,72 @@ import React from 'react';
import { View, StyleSheet } from 'react-native';
import { Card } from 'react-native-paper';
import SkeletonPlaceholder from 'react-native-skeleton-placeholder';
import { colors } from '../utils/color';
const renderSkeletonPresence = (length) => {
return Array.from({ length }).map((_, index) => (
<Card key={index} style={styles.cardPrecense}>
<Card.Content>
<SkeletonPlaceholder>
<View style={[styles.row, { justifyContent: 'space-between' }]}>
<View style={[styles.row, styles.presenceDate, { width: 150, height: 20, borderRadius: 5 }]} />
<View style={[styles.presenceDate, { backgroundColor: '#E0E0E0', width: 80, height: 20, borderRadius: 5 }]} />
</View>
<View style={[styles.row, { justifyContent: 'space-between' }]}>
<View style={[styles.row, { width: 150, height: 20 }]}>
<View style={{ width: 20, height: 20, borderRadius: 5 }} />
<View style={{ width: 100, height: 20, marginLeft: 5, borderRadius: 5 }} />
</View>
<View style={[styles.row, { width: 150, height: 20 }]}>
<View style={{ width: 40, height: 20, borderRadius: 5 }} />
<View style={{ width: 40, height: 20, marginLeft: 5, borderRadius: 5 }} />
<View style={{ width: 40, height: 20, marginLeft: 5, borderRadius: 5 }} />
</View>
</View>
<View style={[styles.row, { justifyContent: 'space-between' }]}>
<View style={{ width: 150, height: 20, borderRadius: 5 }} />
<View style={{ width: 150, height: 20, marginLeft: 5, borderRadius: 5 }} />
</View>
</SkeletonPlaceholder>
</Card.Content>
</Card>
));
const renderSkeletonPresence = (length, theme) => {
return (
<View>
{Array.from({ length }).map((_, index) => (
<Card key={index} style={[styles.cardPresence, { backgroundColor: theme.dark ? theme.colors.black : theme.colors.pureWhite }]}>
<Card.Content>
<SkeletonPlaceholder
borderRadius={5}
backgroundColor={theme.dark ? theme.colors.amnestySmoke : theme.colors.amnestySmoke}
highlightColor={theme.dark ? theme.colors.mistBlue : theme.colors.semiBlue}
>
<View style={[styles.row, { justifyContent: 'space-between' }]}>
<View style={[styles.row, styles.presenceDate]}>
<SkeletonPlaceholder.Item width={20} marginRight={2} height={20} />
<SkeletonPlaceholder.Item width={110} height={20} />
</View>
<View style={[styles.presenceDate, { backgroundColor: colors.semigreen }]}>
<SkeletonPlaceholder.Item width={100} height={20} />
</View>
</View>
<View style={[styles.row, { justifyContent: 'space-between' }]}>
<View style={styles.row}>
<SkeletonPlaceholder.Item width={20} marginRight={2} height={20} />
<SkeletonPlaceholder.Item width={100} height={20} />
</View>
<View style={styles.row}>
<SkeletonPlaceholder.Item width={40} height={20} />
<SkeletonPlaceholder.Item width={20} marginHorizontal={3} height={20} />
<SkeletonPlaceholder.Item width={40} height={20} />
</View>
</View>
<View style={[styles.row, { justifyContent: 'space-between' }]}>
<View style={styles.row}>
<SkeletonPlaceholder.Item width={100} height={20} />
</View>
<View style={styles.row}>
<SkeletonPlaceholder.Item width={40} height={20} />
<SkeletonPlaceholder.Item width={20} marginHorizontal={3} height={20} />
<SkeletonPlaceholder.Item width={40} height={20} />
</View>
</View>
</SkeletonPlaceholder>
</Card.Content>
</Card>
))}
</View>
);
};
const styles = StyleSheet.create({
presenceDate: {
borderRadius: 10,
},
row: {
flexDirection: 'row',
alignItems: 'center',
marginBottom: 5,
},
cardPrecense: {
margin: 10,
padding: 10,
},
presenceDate: {
padding: 5,
borderRadius: 5,
cardPresence: {
marginHorizontal: 10,
marginVertical: 2
},
});

19
src/navigation/BottomTabNavigator.js

@ -1,5 +1,5 @@
import React from 'react';
import { View, StatusBar } from 'react-native';
import { StatusBar, View, useColorScheme } from 'react-native';
import { CommonActions } from '@react-navigation/native';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import { BottomNavigation } from 'react-native-paper';
@ -10,10 +10,17 @@ import NotifikasiScreen from '../screens/Notification';
import ProfileScreen from '../screens/Profile';
import ServiceScreen from '../screens/Service';
import { colors } from '../utils/color';
import { useSelector } from 'react-redux';
const Tab = createBottomTabNavigator();
export default function BottomTabNavigator() {
const { theme, useSystemTheme } = useSelector(state => state.themeReducer);
const tabBarColor = theme === 'dark' ? colors.black : colors.pureWhite;
const activeColor = colors.blue;
return (
<Tab.Navigator
screenOptions={{
@ -22,7 +29,7 @@ export default function BottomTabNavigator() {
screenListeners={({ route, navigation }) => ({
focus: () => {
if (route.name === 'Home') {
StatusBar.setBackgroundColor(colors.blue);
StatusBar.setBackgroundColor(theme === 'dark' ? colors.black : colors.blue);
StatusBar.setBarStyle('light-content');
} else {
StatusBar.setBackgroundColor('white');
@ -56,14 +63,14 @@ export default function BottomTabNavigator() {
renderIcon={({ route, focused }) => {
const { options } = descriptors[route.key];
if (options.tabBarIcon) {
return options.tabBarIcon({ focused, color: colors.blue, size: 24 });
return options.tabBarIcon({ focused, color: activeColor, size: 24 });
}
return null;
}}
renderIndicator={({ route, focused }) => {
if (focused) {
return <View style={{ height: 4, backgroundColor: colors.blue }} />;
return <View style={{ height: 4, backgroundColor: colors.semiBlue }} />;
}
return null;
}}
@ -79,10 +86,10 @@ export default function BottomTabNavigator() {
return label;
}}
style={{ backgroundColor: colors.pureWhite, borderTopWidth: 0, elevation: 8 }}
style={{ backgroundColor: tabBarColor, borderTopWidth: 0, elevation: 8 }}
labeled={false}
compact={true}
activeColor={colors.beanRed}
activeColor={activeColor}
/>
)}
>

53
src/screens/Home.js

@ -7,7 +7,7 @@ import {
PermissionsAndroid,
RefreshControl
} from 'react-native';
import { Card, Avatar, IconButton, Button, Text, TouchableRipple, ActivityIndicator } from 'react-native-paper';
import { Card, Avatar, IconButton, Button, Text, TouchableRipple, ActivityIndicator, useTheme } from 'react-native-paper';
import { launchCamera } from 'react-native-image-picker';
import { useFocusEffect } from '@react-navigation/native';
import ImageMarker, { Position, TextBackgroundType } from 'react-native-image-marker';
@ -24,18 +24,19 @@ import {
import Icon from 'react-native-vector-icons/AntDesign';
import Toast from 'react-native-toast-message';
import person from '../assets/images/courier_man.png';
import { colors } from '../utils/color';
import { getCoords } from '../utils/geolocation';
import { strings } from '../utils/i18n';
import { PATH_PRESENCE } from '../config/imageFolder';
import { storeData, resendFailedAttachments } from '../services/sqlite/attachment';
// import { storeData, resendFailedAttachments } from '../services/sqlite/attachment';
import RequestModule from '../services/api/request';
import { requestAccessStoragePermission } from '../utils/storage';
// import renderSkeleton from '../components/renderSkeleton';
import renderSkeleton from '../components/renderSkeleton';
moment.locale('id');
const HomeScreen = ({ route, navigation }) => {
const theme = useTheme();
const isDarkTheme = theme.dark;
const request = new RequestModule('presence')
const { user } = useSelector(state => state.userReducer)
const [currentTime, setCurrentTime] = useState(moment(new Date()));
@ -74,7 +75,7 @@ const HomeScreen = ({ route, navigation }) => {
}
getPresenceHistory()
getLastPresence()
resendFailedAttachments();
// resendFailedAttachments();
}, [])
@ -173,7 +174,7 @@ const HomeScreen = ({ route, navigation }) => {
console.error("Error sending presence data:", error);
Toast.show({
type: 'error',
text1: strings('presence.errorMessage'),
text1: strings('global.errorConnectionMsg'),
});
setLoading(false)
}
@ -377,13 +378,13 @@ const HomeScreen = ({ route, navigation }) => {
return (
<View style={styles.container}>
<StatusBar backgroundColor={colors.blue} barStyle='ligth-content' translucent={true} />
<View style={[styles.header, { backgroundColor: colors.blue }]}>
<View style={[styles.container, { backgroundColor: isDarkTheme ? theme.colors.surface : theme.colors.pureWhite }]}>
<StatusBar backgroundColor={isDarkTheme ? theme.colors.background : theme.colors.blue} barStyle={isDarkTheme ? 'light-content' : 'light-content'} translucent={true} />
<View style={[styles.header, { backgroundColor: isDarkTheme ? theme.colors.background : theme.colors.blue }]}>
<Avatar.Image style={styles.avatar} source={{ uri: 'https://nawakara-staging-api.ospro.id/assets/files/presence/2024-06/OQ8oglNiCIoCrPX1718098448814614133-0.jpg' }} />
<View style={styles.textContainer}>
<Text style={[styles.welcomeText, { color: colors.pureWhite }]}>
{strings('home.welcomeMessage')}, {user?.name}
{strings('home.welcomeMessage')}, {user?.name.length > 5 ? `${user?.name.substring(0, 5)}...` : user?.name}
</Text>
<Text style={[styles.subWelcomeText, { color: colors.pureWhite }]}>
penuh semangat dan produktivitas
@ -397,7 +398,7 @@ const HomeScreen = ({ route, navigation }) => {
/>
</View>
<Card elevation={4} style={[styles.card, { backgroundColor: 'white' }]}>
<Card elevation={4} style={[styles.card, { backgroundColor: isDarkTheme ? theme.colors.background : theme.colors.pureWhite }]}>
<Card.Content>
<Text variant='displaySmall' style={[styles.boldText, { paddingBottom: 10, textAlign: 'center', color: colors.blue }]}>{moment(currentTime).format('HH:mm')}</Text>
<Text variant='titleSmall' style={{ paddingBottom: 10 }}>{moment(currentTime).format('dddd, DD MMMM - YYYY')} </Text>
@ -431,12 +432,12 @@ const HomeScreen = ({ route, navigation }) => {
</Card>
<View style={[styles.row, { justifyContent: 'space-between', paddingBottom: 5, paddingHorizontal: 15 }]}>
<Text style={{ color: colors.black, fontWeight: 'bold' }}>Riwayat Kehadiran</Text>
<Text style={{ color: isDarkTheme ? theme.colors.pureWhite : theme.colors.black, fontWeight: 'bold' }}>Riwayat Kehadiran</Text>
<TouchableRipple onPress={() => { navigation.navigate('PresenceScreen') }}>
<Text style={{ color: colors.amethystSmoke }}>Lihat Semua</Text>
</TouchableRipple>
</View>
<ScrollView style={{ flex: 1 }}
<ScrollView style={{ flex: 1, backgroundColor: isDarkTheme ? theme.colors.surface : theme.colors.pureWhite }}
refreshControl={
<RefreshControl
refreshing={refreshing}
@ -447,15 +448,15 @@ const HomeScreen = ({ route, navigation }) => {
}
>
{loadingSkeleton ? (
// renderSkeleton(2)
null
renderSkeleton(2, theme)
) : (
dataPresence.map((item) => (
<Card key={item.id} style={styles.cardPrecense}>
<Card key={item.id} style={[styles.cardPrecense, { backgroundColor: isDarkTheme ? theme.colors.background : theme.colors.pureWhite }]}>
<Card.Content>
<View style={[styles.row, { justifyContent: 'space-between' }]}>
<View style={[styles.row, styles.precenseDate]}>
<Icon name="calendar" size={20} color={colors.blue} />
<Text style={{ color: colors.blue, fontWeight: 'bold', paddingLeft: 3 }}>
{moment(item.datePresence).format('dddd, DD MMMM - YYYY')}
</Text>
@ -469,28 +470,29 @@ const HomeScreen = ({ route, navigation }) => {
<View style={[styles.row, { justifyContent: 'space-between' }]}>
<View style={styles.row}>
<Icon name="clockcircleo" size={20} color={colors.black} />
<Text style={{ color: colors.amethystSmoke, fontWeight: 'bold', paddingLeft: 3 }}>
<Icon name="clockcircleo" size={20} color={isDarkTheme ? theme.colors.pureWhite : theme.colors.black} />
<Text style={{ color: isDarkTheme ? theme.colors.pureWhite : theme.colors.amethystSmoke, fontWeight: 'bold', paddingLeft: 3, marginLeft: 3 }}>
Durasi Kerja
</Text>
</View>
<View style={styles.row}>
<Text style={{ color: colors.amethystSmoke }}>Masuk</Text>
<Icon name="minus" size={20} color={colors.amethystSmoke} />
<Text style={{ color: colors.amethystSmoke }}>Keluar</Text>
<Text style={{ color: isDarkTheme ? theme.colors.pureWhite : theme.colors.amethystSmoke }}>Masuk</Text>
<Icon name="minus" size={20} color={isDarkTheme ? theme.colors.pureWhite : theme.colors.amethystSmoke} />
<Text style={{ color: isDarkTheme ? theme.colors.pureWhite : theme.colors.amethystSmoke }}>Keluar</Text>
</View>
</View>
<View style={[styles.row, { justifyContent: 'space-between' }]}>
<View style={styles.row}>
<Text style={{ color: colors.black, paddingLeft: 5 }}>{item.duration ? convertDuration(item.duration) : ''}</Text>
<Text style={{ color: isDarkTheme ? theme.colors.pureWhite : theme.colors.amethystSmoke, paddingLeft: 5 }}>{item.duration ? convertDuration(item.duration) : ''}</Text>
</View>
<View style={styles.row}>
<Text>{moment(item.in_time).format('HH:mm')}</Text>
<Icon name="minus" size={20} color={colors.black} />
<Text>{item.out_time ? moment(item.out_time).format('HH:mm') : ''}</Text>
<Text style={{ color: isDarkTheme ? theme.colors.pureWhite : theme.colors.amethystSmoke }}>{moment(item.in_time).format('HH:mm')}</Text>
<Icon name="minus" size={20} color={isDarkTheme ? theme.colors.pureWhite : theme.colors.amethystSmoke} />
<Text style={{ color: isDarkTheme ? theme.colors.pureWhite : theme.colors.amethystSmoke }}>{item.out_time ? moment(item.out_time).format('HH:mm') : ''}</Text>
</View>
</View>
</Card.Content>
</Card>
))
)}
@ -593,7 +595,6 @@ const styles = StyleSheet.create({
marginHorizontal: 10,
marginVertical: 2,
elevation: 4,
backgroundColor: colors.pureWhite
},
loading: {
position: 'absolute',

87
src/screens/Login.js

@ -1,21 +1,19 @@
import React, { useEffect, useState } from 'react';
import { useForm, Controller } from 'react-hook-form';
import { KeyboardAvoidingView, ScrollView, StatusBar, View, StyleSheet, Image, Platform, Dimensions, Alert } from 'react-native';
import { Button, TextInput, HelperText, Text, Modal } from 'react-native-paper';
import { KeyboardAvoidingView, ScrollView, StatusBar, View, StyleSheet, Image, Platform, Dimensions } from 'react-native';
import { Button, TextInput, HelperText, Text, useTheme } from 'react-native-paper';
import { useDispatch } from 'react-redux';
import { setIsLogin, setUser, setRegister } from '../appredux/actions';
import { store } from '../appredux/store';
import { colors } from '../utils/color';
import LOGIN_BANNER from '../assets/images/logo_nawakara.png';
import { strings } from '../utils/i18n';
import { initFirebase } from '../utils/Auth';
import RequestModule from '../services/api/request';
import Toast from 'react-native-toast-message';
const LoginScreen = ({ route, navigation }) => {
const LoginScreen = ({ navigation }) => {
const theme = useTheme();
const dispatch = useDispatch();
const request = new RequestModule('');
const [visible, setVisible] = useState(false);
const showModal = () => setVisible(true);
const hideModal = () => setVisible(false);
const [loginProcess, setLoginProcess] = useState(false);
const [showPassword, setShowPassword] = useState(false);
const { control, handleSubmit, formState: { errors, isDirty, isValid, isSubmitting } } = useForm({
@ -30,19 +28,18 @@ const LoginScreen = ({ route, navigation }) => {
initFirebase();
}, []);
const onSubmitLogin = async (payload) => {
setLoginProcess(true);
try {
const result = await request.login(payload);
if (result && result.status === 200 && result.data.data) {
store.dispatch(setIsLogin(true));
store.dispatch(setUser(result.data.data));
dispatch(setIsLogin(true));
dispatch(setUser(result.data.data));
if (result.data.data.assigment_hr) {
store.dispatch(setRegister(true));
dispatch(setRegister(true));
navigation.navigate('App');
} else {
store.dispatch(setRegister(false));
dispatch(setRegister(false));
navigation.navigate('RegisterScreen');
}
} else {
@ -65,29 +62,27 @@ const LoginScreen = ({ route, navigation }) => {
return (
<>
<StatusBar backgroundColor={colors.white} barStyle='dark-content' translucent={true} />
<StatusBar backgroundColor={theme.colors.background} barStyle={theme.dark ? 'light-content' : 'dark-content'} translucent={true} />
<KeyboardAvoidingView behavior={Platform.OS === "ios" ? "padding" : null} style={{ flex: 1 }}>
<ScrollView contentContainerStyle={{ flexGrow: 1 }}>
<View style={styles.container}>
<View style={[styles.container, { backgroundColor: theme.colors.background }]}>
<Image
alt="login-image"
source={LOGIN_BANNER}
style={{ width: '100%', height: Dimensions.get('window').height / 5, marginTop: 30, marginBottom: 20 }}
style={styles.loginBanner}
resizeMode="contain"
/>
<Text variant="displaySmall" style={{ color: colors.black, fontFamily: 'Roboto-Bold' }}>{strings('loginPage.headMessage')}</Text>
<Text variant="bodyMedium" style={{ color: colors.mistBlue }}>Silahkan isi Username dan kata sandi anda</Text>
<Text variant="displaySmall" style={[styles.headMessage, { color: theme.colors.text }]}>{strings('loginPage.headMessage')}</Text>
<Text variant="bodyMedium" style={{ color: theme.colors.text }}>Silahkan isi Username dan kata sandi anda</Text>
<Controller
control={control}
rules={{
required: true,
minLength: 0
}}
rules={{ required: true }}
render={({ field: { onChange, onBlur, value } }) => (
<TextInput
underlineColor={colors.amethystSmoke}
activeOutlineColor={colors.blue}
underlineColor={theme.colors.placeholder}
activeOutlineColor={theme.colors.blue}
mode="outlined"
label="Username"
onBlur={onBlur}
@ -100,20 +95,19 @@ const LoginScreen = ({ route, navigation }) => {
)}
name="username"
/>
{errors.username &&
{errors.username && (
<HelperText type="error" padding='none' visible={!!errors.username}>
{strings('loginPage.usernameErrorMsg')}
</HelperText>}
</HelperText>
)}
<Controller
control={control}
rules={{
required: true,
minLength: 3
}}
rules={{ required: true, minLength: 3 }}
render={({ field: { onChange, onBlur, value } }) => (
<TextInput
underlineColor={colors.amethystSmoke}
activeOutlineColor={colors.blue}
underlineColor={theme.colors.placeholder}
activeOutlineColor={theme.colors.blue}
mode="outlined"
label="Password"
onBlur={onBlur}
@ -132,27 +126,26 @@ const LoginScreen = ({ route, navigation }) => {
)}
name="password"
/>
{errors.password &&
{errors.password && (
<HelperText type="error" padding='none' visible={!!errors.password}>
{strings('loginPage.passwordErrorMsg')}
</HelperText>}
</HelperText>
)}
<Button
mode="contained"
onPress={handleSubmit(onSubmitLogin)}
style={{ marginTop: 15, backgroundColor: colors.blue, borderRadius: 10 }}
style={[styles.signInButton, { backgroundColor: theme.colors.blue }]}
disabled={!isDirty || !isValid || isSubmitting || loginProcess}
labelStyle={{ color: colors.white }}
labelStyle={{ color: theme.colors.background }}
loading={isSubmitting || loginProcess}
>
{strings('loginPage.signInBtn')}
</Button>
</View>
</ScrollView >
</ScrollView>
</KeyboardAvoidingView>
</>
)
}
@ -161,12 +154,24 @@ const styles = StyleSheet.create({
flex: 1,
padding: 20,
justifyContent: 'center',
backgroundColor: colors.white,
paddingBottom: 100
},
eyeIcon: {
marginTop: 12
},
loginBanner: {
width: '100%',
height: Dimensions.get('window').height / 5,
marginTop: 30,
marginBottom: 20
},
headMessage: {
fontFamily: 'Roboto-Bold'
},
signInButton: {
marginTop: 15,
borderRadius: 10
}
})
});
export default LoginScreen;

154
src/screens/Profile.js

@ -1,110 +1,115 @@
import React, { useState } from 'react';
import { Alert, View, ScrollView, StyleSheet, TouchableOpacity, StatusBar } from 'react-native';
import { Avatar, Text, Appbar, List, Button, Switch, Modal, Dialog, TouchableRipple } from 'react-native-paper';
import AntDesign from 'react-native-vector-icons/AntDesign';
import Ionicons from 'react-native-vector-icons/Ionicons';
import { clearAllState } from '../utils/Auth';
import React, { useEffect, useState } from 'react';
import { Alert, View, ScrollView, StyleSheet, StatusBar } from 'react-native';
import { Avatar, Text, Appbar, List, Button, Dialog, TouchableRipple, PaperProvider, useTheme } from 'react-native-paper';
import { strings } from '../utils/i18n';
import person from '../assets/images/courier_man.png'
import { colors } from '../utils/color';
import { useSelector } from 'react-redux';
import { clearAllState } from '../utils/Auth';
const ProfileScreen = ({ navigation }) => {
const [isSwitchOn, setIsSwitchOn] = React.useState(false);
const { user } = useSelector(state => state.userReducer)
const onToggleSwitch = () => setIsSwitchOn(!isSwitchOn);
const theme = useTheme();
const isDarkTheme = theme.dark;
const { user } = useSelector(state => state.userReducer);
const [visible, setVisible] = useState(false);
const showModal = () => setVisible(true);
const hideModal = () => setVisible(false);
console.log("user", user.join);
const showDialog = () => setVisible(true);
const hideDialog = () => setVisible(false);
return (
<>
<PaperProvider>
<View style={styles.container}>
<StatusBar backgroundColor={colors.pureWhite} barStyle='dark-content' translucent={true} />
<Appbar.Header mode='center-aligned' style={{ backgroundColor: colors.pureWhite }} >
<Appbar.BackAction onPress={() => { navigation.goBack() }} />
<Appbar.Content title="Profile" />
<StatusBar backgroundColor={isDarkTheme ? theme.colors.background : theme.colors.blue} barStyle={isDarkTheme ? 'dark-content' : 'dark-content'} translucent={true} />
<Appbar.Header mode='center-aligned' style={{ backgroundColor: isDarkTheme ? theme.colors.background : theme.colors.pureWhite }}>
<Appbar.BackAction color={isDarkTheme ? theme.colors.pureWhite : theme.colors.black} onPress={() => { navigation.goBack() }} />
<Appbar.Content titleStyle={{ fontWeight: 'bold' }} color={isDarkTheme ? theme.colors.pureWhite : theme.colors.black} title={strings('profile.profile')} />
</Appbar.Header>
<View style={styles.header}>
<View style={[styles.header, { backgroundColor: isDarkTheme ? theme.colors.background : theme.colors.pureWhite }]}>
<Avatar.Image style={styles.avatar} size={80} source={{ uri: 'https://nawakara-staging-api.ospro.id/assets/files/presence/2024-06/OQ8oglNiCIoCrPX1718098448814614133-0.jpg' }} />
<View style={styles.textContainer}>
<Text style={[styles.welcomeText, { color: colors.black }]}>
<Text style={[styles.welcomeText, { color: isDarkTheme ? theme.colors.pureWhite : theme.colors.black }]}>
{user.name}
</Text>
<Text style={[styles.subWelcomeText, { color: colors.amethystSmoke }]}>
{user.join.m_role_name}
<Text style={[styles.subWelcomeText, { color: isDarkTheme ? theme.colors.pureWhite : theme.colors.amethystSmoke }]}>
{user.assigment_hr.position}
</Text>
</View>
</View>
<ScrollView style={{ backgroundColor: colors.pureWhite }}>
<ScrollView style={{ backgroundColor: isDarkTheme ? theme.colors.background : theme.colors.pureWhite }}>
<View style={styles.cardView}>
<TouchableRipple rippleColor={colors.pureWhite} onPress={() => { navigation.navigate('DailyReportScreen') }}>
<TouchableRipple rippleColor={isDarkTheme ? theme.colors.amethystSmoke : theme.colors.pureWhite} onPress={() => { navigation.navigate('DailyReportScreen') }}>
<List.Item
title="Laporan Harian"
left={() => <List.Icon icon="file-document" />}
right={() => <List.Icon icon="chevron-right" />}
title={strings('dailyReport.title')}
titleStyle={{ color: isDarkTheme ? theme.colors.pureWhite : theme.colors.black }}
left={() => <List.Icon icon="file-document" color={isDarkTheme ? theme.colors.pureWhite : theme.colors.black} />}
right={() => <List.Icon icon="chevron-right" color={isDarkTheme ? theme.colors.pureWhite : theme.colors.black} />}
/>
</TouchableRipple>
<TouchableRipple rippleColor={colors.pureWhite} onPress={() => { navigation.navigate('PresenceScreen') }}>
<List.Item
title="Riwayat Kehadiran"
left={() => <List.Icon icon="history" />}
right={() => <List.Icon icon="chevron-right" />}
title={strings('presence.attendanceHistory')}
titleStyle={{ color: isDarkTheme ? theme.colors.pureWhite : theme.colors.black }}
left={() => <List.Icon icon="history" color={isDarkTheme ? theme.colors.pureWhite : theme.colors.black} />}
right={() => <List.Icon icon="chevron-right" color={isDarkTheme ? theme.colors.pureWhite : theme.colors.black} />}
/>
</TouchableRipple>
<TouchableRipple rippleColor={colors.pureWhite} onPress={() => { navigation.navigate('RegisterScreen') }}>
<List.Item
title="Registrasi"
left={() => <List.Icon icon="account-cog" />}
right={() => <List.Icon icon="chevron-right" />}
title={strings('register.title')}
titleStyle={{ color: isDarkTheme ? theme.colors.pureWhite : theme.colors.black }}
left={() => <List.Icon icon="account-cog" color={isDarkTheme ? theme.colors.pureWhite : theme.colors.black} />}
right={() => <List.Icon icon="chevron-right" color={isDarkTheme ? theme.colors.pureWhite : theme.colors.black} />}
/>
</TouchableRipple>
<TouchableRipple rippleColor={colors.pureWhite} onPress={() => { navigation.navigate('RegisterScreen') }}>
{user.assigment_hr.position !== 'Guard' &&
<TouchableRipple rippleColor={colors.pureWhite} onPress={() => { navigation.navigate('RegisterScreen') }}>
<List.Item
title={strings('profile.personnelList')}
titleStyle={{ color: isDarkTheme ? theme.colors.pureWhite : theme.colors.black }}
left={() => <List.Icon icon="account" color={isDarkTheme ? theme.colors.pureWhite : theme.colors.black} />}
right={() => <List.Icon icon="chevron-right" color={isDarkTheme ? theme.colors.pureWhite : theme.colors.black} />}
/>
</TouchableRipple>
}
<TouchableRipple rippleColor={colors.pureWhite} onPress={() => { navigation.navigate('ThemeScreen') }}>
<List.Item
title="Daftar Personel"
left={() => <List.Icon icon="account" />}
right={() => <List.Icon icon="chevron-right" />}
title={strings('profile.changeTheme')}
titleStyle={{ color: isDarkTheme ? theme.colors.pureWhite : theme.colors.black }}
left={() => <List.Icon icon="theme-light-dark" color={isDarkTheme ? theme.colors.pureWhite : theme.colors.black} />}
right={() => <List.Icon icon="chevron-right" color={isDarkTheme ? theme.colors.pureWhite : theme.colors.black} />}
/>
</TouchableRipple>
<TouchableRipple rippleColor={colors.pureWhite} onPress={() => { navigation.navigate('LanguageScreen') }}>
<List.Item
title={strings('profile.languageSetting')}
titleStyle={{ color: isDarkTheme ? theme.colors.pureWhite : theme.colors.black }}
left={() => <List.Icon icon="translate" color={isDarkTheme ? theme.colors.pureWhite : theme.colors.black} />}
right={() => <List.Icon icon="chevron-right" color={isDarkTheme ? theme.colors.pureWhite : theme.colors.black} />}
/>
</TouchableRipple>
<List.Item
title="bahasa"
left={() => <List.Icon icon="translate" />}
right={() =>
<View style={styles.switchContainer}>
<Text>{isSwitchOn ? "ID" : "EN"}</Text>
<Switch
trackColor={{ true: colors.blue, false: colors.blue }}
color={colors.pureWhite}
style={styles.switch}
onValueChange={onToggleSwitch}
value={isSwitchOn}
/>
</View>
}
/>
</View>
</ScrollView>
<Button buttonColor={colors.beanRed} style={styles.button} contentStyle={{ flexDirection: 'row-reverse' }} icon="logout" mode="contained" onPress={showModal}>
Keluar
</Button>
<View style={{ backgroundColor: isDarkTheme ? theme.colors.background : theme.colors.pureWhite }}>
<Button buttonColor={colors.beanRed} textColor={theme.colors.pureWhite} style={styles.button} contentStyle={{ flexDirection: 'row-reverse' }} icon="logout" mode="contained" onPress={showDialog}>
{strings('global.logout')}
</Button>
</View>
</View >
<Dialog visible={visible} onDismiss={hideDialog} style={[styles.modal, { backgroundColor: isDarkTheme ? theme.colors.background : theme.colors.pureWhite }]}>
<Dialog.Title style={{ color: isDarkTheme ? theme.colors.pureWhite : theme.colors.black }}>{strings('global.confirm')}</Dialog.Title>
<Dialog.Content>
<Text variant="bodyMedium" style={{ color: isDarkTheme ? theme.colors.pureWhite : theme.colors.black }}>{strings('profile.signoutMessage')}</Text>
<Button buttonColor={colors.blue} textColor={theme.colors.pureWhite} style={{ borderRadius: 10, marginVertical: 20, paddingVertical: 5 }} contentStyle={{ flexDirection: 'row-reverse' }} mode="contained" onPress={() => { clearAllState() }}>
{strings('global.yes')}
</Button>
<Button style={{ borderRadius: 10, paddingVertical: 5 }} contentStyle={{ flexDirection: 'row-reverse' }} textColor={isDarkTheme ? theme.colors.semiBlue : theme.colors.blue} mode="outlined" onPress={hideDialog}>
{strings('global.no')}
</Button>
</Dialog.Content>
</Dialog>
<Modal visible={visible} onDismiss={hideModal} contentContainerStyle={styles.modal}>
<Text variant='titleLarge' >Konfirmasi</Text>
<Text variant='bodyMedium' >{strings('profile.signoutMessage')}</Text>
<Button buttonColor={colors.blue} style={{ borderRadius: 10, marginVertical: 20, paddingVertical: 5 }} contentStyle={{ flexDirection: 'row-reverse' }} mode="contained" onPress={() => { clearAllState() }}>
{strings('global.yes')}
</Button>
<Button style={{ borderRadius: 10, paddingVertical: 5 }} contentStyle={{ flexDirection: 'row-reverse' }} textColor={colors.mistBlue} mode="outlined" onPress={hideModal}>
{strings('global.no')}
</Button>
</Modal>
</>
</PaperProvider >
)
}
@ -112,14 +117,12 @@ const styles = StyleSheet.create({
container: {
flex: 1,
marginTop: 20,
backgroundColor: colors.pureWhite
},
header: {
flexDirection: 'row',
alignItems: 'center',
paddingHorizontal: 10,
paddingVertical: 20,
backgroundColor: colors.pureWhite
},
avatar: {
marginRight: 10,
@ -144,7 +147,6 @@ const styles = StyleSheet.create({
marginTop: 10,
marginHorizontal: 10,
marginVertical: 2,
backgroundColor: colors.pureWhite
},
button: {
margin: 10,
@ -156,17 +158,9 @@ const styles = StyleSheet.create({
padding: 20,
marginHorizontal: 20,
borderRadius: 10,
backgroundColor: colors.pureWhite
},
switchContainer: {
flexDirection: "row",
justifyContent: "center",
alignItems: "center"
},
switch: {
marginLeft: 10
}
})
export default ProfileScreen

13
src/screens/Service.js

@ -1,6 +1,6 @@
import React, { useEffect, useCallback, useMemo, useState, useRef } from 'react';
import { Icon, Card, Text, Avatar, useTheme, IconButton, MD3Colors, Button, TouchableRipple } from 'react-native-paper';
import { RefreshControl, StyleSheet, View, ScrollView, Image, Dimensions } from 'react-native';
import { RefreshControl, StyleSheet, View, ScrollView, Image, Dimensions, StatusBar } from 'react-native';
import ilustration from '../assets/images/checked.png';
import danger from '../assets/images/danger.png';
import activity from '../assets/images/activity.png';
@ -12,8 +12,10 @@ import { strings } from '../utils/i18n';
export default function ServiceScreen({ route, navigation }) {
const theme = useTheme();
const isDarkTheme = theme.dark
return (
<View style={styles.container}>
<View style={[styles.container, { backgroundColor: isDarkTheme ? theme.colors.background : theme.colors.pureWhite }]}>
<StatusBar backgroundColor={isDarkTheme ? theme.colors.background : theme.colors.blue} barStyle={isDarkTheme ? 'dark-content' : 'dark-content'} translucent={true} />
<Card elevation={4} style={{ marginHorizontal: 10, marginVertical: 6, marginTop: 10, backgroundColor: colors.beanRed }}>
<TouchableRipple onPress={() => { navigation.navigate('IncidentScreen') }}>
<View style={styles.cardContent}>
@ -102,8 +104,8 @@ export default function ServiceScreen({ route, navigation }) {
/>
<Text style={[styles.Text, { color: colors.pureWhite }]}>Kehadiran</Text>
<Text variant="bodySmall" style={[styles.subText, { color: colors.pureWhite }]} >Lihat riwayat kehadiran</Text>
<Button mode="contained" compact={true} style={{ borderRadius: 10, marginTop: 10, marginBottom: 5, backgroundColor: 'white', width: '100%' }} labelStyle={{ color: colors.blue }} onPress={() => { navigation.navigate('PresenceScreen') }}>
{strings('global.see')}
<Button mode="contained" compact={true} style={{ borderRadius: 10, marginTop: 10, backgroundColor: 'white', width: '100%' }} labelStyle={{ color: colors.blue }} onPress={() => { navigation.navigate('PresenceScreen') }}>
{strings('global.fill')}
</Button>
</View>
</TouchableRipple>
@ -134,8 +136,7 @@ export default function ServiceScreen({ route, navigation }) {
const styles = StyleSheet.create({
container: {
flex: 1,
marginTop: 30,
backgroundColor: colors.pureWhite
marginTop: 30
},
Text: {
fontSize: 16,

80
src/screens/presence/index.js

@ -1,4 +1,4 @@
import { TouchableRipple, TextInput, Card, Text, Appbar } from 'react-native-paper';
import { TouchableRipple, TextInput, Card, Text, Appbar, Avatar, ActivityIndicator, useTheme } from 'react-native-paper';
import { View, StyleSheet, ScrollView, RefreshControl, StatusBar, Image } from 'react-native';
import React, { useState, useEffect, useCallback, useRef, useMemo } from 'react';
import { colors } from '../../utils/color'
@ -12,10 +12,13 @@ import {
BottomSheetModalProvider,
BottomSheetView
} from '@gorhom/bottom-sheet';
import { strings } from '../../utils/i18n';
moment.locale('id');
const PAGE_SIZE = 25;
export default function PresenceScreen({ route, navigation }) {
const theme = useTheme();
const isDarkTheme = theme.dark
const request = new RequestModule('presence');
const [refreshing, setRefreshing] = useState(false);
const [page, setPage] = useState(0);
@ -23,10 +26,16 @@ export default function PresenceScreen({ route, navigation }) {
const [loading, setLoading] = useState(false);
const bottomSheetImage = useRef(null);
const [selectedImageUri, setSelectedImageUri] = useState(null);
const [loadingImage, setLoadingImage] = useState(false);
const handleOpenSheetImage = useCallback((uri) => {
console.log('URI received:', uri);
setSelectedImageUri(uri);
bottomSheetImage.current?.present();
setLoadingImage(true);
setTimeout(() => {
setSelectedImageUri(uri);
setLoadingImage(false);
bottomSheetImage.current?.present();
}, 1000);
}, []);
const onRefresh = useCallback(() => {
@ -73,27 +82,34 @@ export default function PresenceScreen({ route, navigation }) {
const isCloseToBottom = ({ layoutMeasurement, contentOffset, contentSize }) => {
return layoutMeasurement.height + contentOffset.y >= contentSize.height - 20;
};
const renderImage = useMemo(() => {
console.log('Rendering image with URI:', selectedImageUri);
return (
<Image
source={{ uri: selectedImageUri }}
style={styles.imageDetail}
resizeMode="cover"
/>
<>
{loadingImage ? (
<ActivityIndicator size="large" color={colors.blue} />
) : (
<>
<Image
source={{ uri: selectedImageUri }}
style={styles.imageDetail}
resizeMode="cover"
/>
</>
)}
</>
);
}, [selectedImageUri]);
}, [selectedImageUri, loadingImage]);
return (
<>
<StatusBar backgroundColor={colors.blue} barStyle='ligth-content' translucent={true} />
<Appbar.Header mode='center-aligned' style={{ backgroundColor: colors.blue }}>
<StatusBar backgroundColor={isDarkTheme ? theme.colors.background : theme.colors.blue} barStyle={isDarkTheme ? 'dark-content' : 'light-content'} translucent={true} />
<Appbar.Header mode='center-aligned' style={{ backgroundColor: isDarkTheme ? theme.colors.background : theme.colors.blue }}>
<Appbar.BackAction color={colors.pureWhite} onPress={() => { navigation.goBack() }} />
<Appbar.Content titleStyle={{ fontWeight: 'bold' }} color={colors.pureWhite} title='Riwayat kehadiran' />
<Appbar.Content titleStyle={{ fontWeight: 'bold' }} color={colors.pureWhite} title={strings('presence.attendanceHistory')} />
</Appbar.Header>
<ScrollView
style={{ flex: 1, marginTop: 5 }}
style={{ flex: 1, marginTop: 5, backgroundColor: theme.colors.surface }}
refreshControl={
<RefreshControl
refreshing={refreshing}
@ -106,12 +122,11 @@ export default function PresenceScreen({ route, navigation }) {
scrollEventThrottle={400}
>
{loading ? (
// renderSkeleton(10)
null
renderSkeleton(10, theme)
) : (
dataPresence.map((item) => (
<TouchableRipple onPress={() => item.attachments && item.attachments[0] && handleOpenSheetImage(item.attachments[0].url)} key={item.id}>
<Card key={item.id} style={styles.cardPrecense}>
<Card key={item.id} style={[styles.cardPrecense, { backgroundColor: isDarkTheme ? theme.colors.background : theme.colors.pureWhite }]}>
<Card.Content>
<View style={[styles.row, { justifyContent: 'space-between' }]}>
<View style={[styles.row, styles.precenseDate]}>
@ -130,35 +145,34 @@ export default function PresenceScreen({ route, navigation }) {
<View style={[styles.row, { justifyContent: 'space-between' }]}>
<View style={styles.row}>
<Icon name="clockcircleo" size={20} color={colors.black} />
<Text style={{ color: colors.amethystSmoke, fontWeight: 'bold', paddingLeft: 3 }}>
<Icon name="clockcircleo" size={20} color={isDarkTheme ? theme.colors.pureWhite : theme.colors.black} />
<Text style={{ color: isDarkTheme ? theme.colors.pureWhite : theme.colors.amethystSmoke, fontWeight: 'bold', paddingLeft: 3, marginLeft: 3 }}>
Durasi Kerja
</Text>
</View>
<View style={styles.row}>
<Text style={{ color: colors.amethystSmoke }}>Masuk</Text>
<Icon name="minus" size={20} color={colors.amethystSmoke} />
<Text style={{ color: colors.amethystSmoke }}>Keluar</Text>
<Text style={{ color: isDarkTheme ? theme.colors.pureWhite : theme.colors.amethystSmoke }}>Masuk</Text>
<Icon name="minus" size={20} color={isDarkTheme ? theme.colors.pureWhite : theme.colors.amethystSmoke} />
<Text style={{ color: isDarkTheme ? theme.colors.pureWhite : theme.colors.amethystSmoke }}>Keluar</Text>
</View>
</View>
<View style={[styles.row, { justifyContent: 'space-between' }]}>
<View style={styles.row}>
<Text style={{ color: colors.black, paddingLeft: 5 }}>{item.duration ? convertDuration(item.duration) : ''}</Text>
<Text style={{ color: isDarkTheme ? theme.colors.pureWhite : theme.colors.amethystSmoke, paddingLeft: 5 }}>{item.duration ? convertDuration(item.duration) : ''}</Text>
</View>
<View style={styles.row}>
<Text>{moment(item.in_time).format('HH:mm')}</Text>
<Icon name="minus" size={20} color={colors.black} />
<Text>{item.out_time ? moment(item.out_time).format('HH:mm') : ''}</Text>
<Text style={{ color: isDarkTheme ? theme.colors.pureWhite : theme.colors.amethystSmoke }}>{moment(item.in_time).format('HH:mm')}</Text>
<Icon name="minus" size={20} color={isDarkTheme ? theme.colors.pureWhite : theme.colors.amethystSmoke} />
<Text style={{ color: isDarkTheme ? theme.colors.pureWhite : theme.colors.amethystSmoke }}>{item.out_time ? moment(item.out_time).format('HH:mm') : ''}</Text>
</View>
</View>
<Text>{item.attachments && item.attachments.length > 0 ? item.attachments[0].url : 'No attachments'}</Text>
</Card.Content>
</Card>
</TouchableRipple>
))
)}
</ScrollView>
</ScrollView >
<BottomSheetModalProvider>
<BottomSheetModal
@ -239,6 +253,10 @@ const styles = StyleSheet.create({
marginHorizontal: 10,
marginVertical: 2,
elevation: 4,
backgroundColor: colors.pureWhite
},
imageDetail: {
width: 330,
height: 300,
borderRadius: 7
}
});

248
src/screens/registerPage/index.js

@ -1,9 +1,11 @@
import React, { useEffect, useCallback, useMemo, useState, useRef } from 'react';
import { StatusBar } from 'react-native';
import { Button, IconButton, Text, Appbar, TextInput, TouchableRipple, Card, List } from 'react-native-paper';
import { Button, IconButton, Text, Appbar, TextInput, TouchableRipple, Card, List, useTheme } from 'react-native-paper';
import { StyleSheet, View, ScrollView, PermissionsAndroid, Image } from 'react-native';
import { colors } from '../../utils/color';
import { strings } from '../../utils/i18n';
import { store } from '../../appredux/store';
import { setRegister } from '../../appredux/actions';
import { launchCamera } from 'react-native-image-picker';
import { requestAccessStoragePermission } from '../../utils/storage';
import { getCoords } from '../../utils/geolocation';
@ -14,16 +16,19 @@ import ImageMarker, { Position, TextBackgroundType } from 'react-native-image-ma
import DateTimePicker from '@react-native-community/datetimepicker';
import moment from 'moment';
import uuid from 'react-native-uuid';
import Toast from 'react-native-toast-message';
import RequestModule
from '../../services/api/request';
import { PATH_ID } from '../../config/imageFolder';
import { useSelector } from 'react-redux';
export default function DialogForm() {
const theme = useTheme();
const isDarkTheme = theme.dark;
const request = new RequestModule('project-charter')
const requestAssign = new RequestModule('assign-hr-to-project')
const requestAssign = new RequestModule('assignHR')
const navigation = useNavigation();
const { user } = useSelector(state => state.userReducer)
const [images, setImages] = useState([]);
const [image, setImage] = useState(null);
const [selectedImageUri, setSelectedImageUri] = useState(null);
const bottomSheetImage = useRef(null);
const [selectedProject, setSelectedProject] = useState(null);
@ -59,10 +64,11 @@ export default function DialogForm() {
}, []);
useEffect(() => {
handleGetProjectCharter()
handleGetAssignHR()
if (existingAttachmentNumber === '') {
setExistingAttachmentNumber(uuid.v4())
}
handleGetProjectCharter()
}, [])
@ -70,41 +76,98 @@ export default function DialogForm() {
navigation.navigate('SearchPage', { dataListProjectCharters, onSelect: handleProjectSelect });
};
const handleGetAssignHR = async () => {
const payload = {
paging: { start: 0, length: 1 },
columns: [
{ name: 'user_id', logic_operator: '=', operator: 'AND', value: user?.id.toString() },
{ name: 'deleted_at', logic_operator: 'IS NULL', operator: 'AND' },
],
orders: { columns: ['created_at', 'id'], ascending: false }
};
const result = await requestAssign.getDataSearch(payload);
if (result && result.status === 200) {
let projectName
if (result.data.data[0]?.project_charter_id) {
projectName = await getProjectName(result.data.data[0]?.project_charter_id);
}
setImage({ imageFile: result.data.data[0]?.attachments[0]?.url })
setSelectedProject({ id: result.data.data[0]?.project_charter_id, project_name: projectName || '' });
setShift(result.data?.data[0]?.shift)
setPosition(result.data?.data[0]?.position)
setExpDate(result.data?.data[0]?.created_at)
setArea(result.data?.data[0]?.area)
setSelectedPost(result.data?.data[0]?.pos)
setExistingAttachmentNumber(result.data?.data[0]?.attachment_number)
}
}
const handleSendRegisterData = async () => {
const payload = {
project_charter_id: selectedProject.id,
hr_id: user.id,
Position: position,
Area: area,
Post: selectedPost,
position: position,
area: area,
post: selectedPost,
shift: shift,
attachment_number: existingAttachmentNumber
};
try {
const resultImage = await handleUploadImage([image], PATH_ID);
const result = await requestAssign.addData(payload);
if (result.status === 201) {
store.dispatch(setRegister(true));
Toast.show({
type: 'success',
text1: strings('presence.dataSentSuccessfully'),
text1: strings('register.dataSentSuccessfully'),
});
navigation.navigate('App');
clearForm()
} else {
Toast.show({
type: 'error',
text1: strings('presence.failedSendDataPresence'),
text1: strings('presence.failedSendData'),
});
}
} catch (error) {
console.error("Network error sending presence data:", error);
Toast.show({
type: 'error',
text1: strings('presence.errorMessage'),
text1: strings('global.errorConnectionMsg'),
});
} finally {
setLoading(false); // Ensure setLoading is called once in the end
// setLoading(false); // Ensure setLoading is called once in the end
}
};
const getProjectName = async (projectCharterId) => {
const payload = {
paging: { start: 0, length: 1 },
columns: [
{ name: 'id', logic_operator: '=', operator: 'AND', value: projectCharterId.toString() },
{ name: 'deleted_at', logic_operator: 'IS NULL', operator: 'AND' },
],
orders: { columns: ['created_at', 'id'], ascending: false }
};
const result = await request.getDataSearch(payload);
if (result && result.status === 200 && result.data.data.length > 0) {
return result.data.data[0].project_name;
}
return '';
};
const clearForm = () => {
setImage(null)
setSelectedProject(null)
setShift('')
setPosition('')
setExpDate('')
setArea('')
setSelectedPost('')
setExistingAttachmentNumber('')
}
const handleProjectSelect = (project) => {
handleGetDataManPowers(project.id)
@ -185,18 +248,11 @@ export default function DialogForm() {
}
};
const handleSetImageUri = (uri, description) => {
const newImage = { uri: uri, description: description || "" };
setImages(prevImages => [...prevImages, newImage]);
}
const handleDeleteImage = (index) => {
const updatedImages = [...images];
updatedImages.splice(index, 1);
setImages(updatedImages);
const handleDeleteImage = () => {
setImage(null);
};
const handleTakePicture = async () => {
try {
const granted = await PermissionsAndroid.request(
@ -253,7 +309,7 @@ export default function DialogForm() {
console.log('ImagePicker Error: ', response.error);
} else {
if (response.assets && response.assets.length > 0) {
setLoading(true)
// setLoading(true)
try {
getCoords(async (loc) => {
if (loc) {
@ -290,7 +346,7 @@ export default function DialogForm() {
// const tempPath = `file://${RNFS.TemporaryDirectoryPath}/prsensi/${moment().format('YYYYMMDDHHmmss')}.jpg`;
// await RNFS.copyFile(markedImage, tempPath);
console.log("markedImage", markedImage);
const newImageData = {
id: 0,
attachment_number: existingAttachmentNumber,
@ -300,27 +356,25 @@ export default function DialogForm() {
type: response.assets[0].type,
name: response.assets[0].fileName
};
const resultImage = await handleUploadImage([newImageData], PATH_ID);
handleSendRegisterData();
setImage(newImageData);
}
});
} catch (error) {
console.error('Error adding overlay:', error);
setLoading(false)
// setLoading(false)
}
}
}
});
};
const renderImages = useMemo(() => images.map((image, index) => (
const renderImage = useMemo(() => (
<>
<View key={index} style={styles.imageBlock}>
<TouchableRipple onPress={() => { handleOpenSheetImage(image.uri) }}>
<View style={styles.imageBlock}>
<TouchableRipple onPress={() => handleOpenSheetImage(image?.imageFile)}>
<View style={styles.imageContainer}>
<Image
source={{ uri: image.uri }}
source={{ uri: image?.imageFile }}
style={styles.image}
resizeMode="cover"
/>
@ -328,11 +382,14 @@ export default function DialogForm() {
</TouchableRipple>
</View>
<Button icon="delete" style={{ borderRadius: 10, backgroundColor: colors.semiRed, marginHorizontal: 5, marginBottom: 10 }} textColor={colors.beanRed} mode="contained-tonal" onPress={() => handleDeleteImage(index)}>
<Button icon="delete" style={{ borderRadius: 10, backgroundColor: colors.semiRed, marginHorizontal: 5, marginBottom: 10 }} textColor={colors.beanRed} mode="contained-tonal" onPress={handleDeleteImage}>
{strings('global.delete')}
</Button>
</>
)), [images]);
), [image, handleOpenSheetImage, handleDeleteImage]);
const renderArea = useMemo(() => listArea?.manpower_planning?.info?.data.map((data, index) => (
<>
@ -356,15 +413,15 @@ export default function DialogForm() {
return (
<>
<StatusBar backgroundColor={colors.blue} barStyle='light-content' translucent={true} />
<Appbar.Header mode='center-aligned' style={{ backgroundColor: colors.blue, elevation: 4 }}>
<StatusBar backgroundColor={isDarkTheme ? theme.colors.surface : theme.colors.blue} barStyle={isDarkTheme ? 'dark-content' : 'light-content'} translucent={true} />
<Appbar.Header mode='center-aligned' style={{ backgroundColor: isDarkTheme ? theme.colors.surface : theme.colors.blue, elevation: 4 }}>
<Appbar.BackAction color={colors.pureWhite} onPress={() => { navigation.goBack() }} />
<Appbar.Content titleStyle={{ fontWeight: 'bold' }} color={colors.pureWhite} title={strings('global.register')} />
<Appbar.Content titleStyle={{ fontWeight: 'bold' }} color={colors.pureWhite} title={strings('register.title')} />
</Appbar.Header>
<View style={{ flex: 1, backgroundColor: colors.pureWhite }}>
<View style={{ flex: 1, backgroundColor: theme.colors.surface }}>
<View style={styles.container}>
<ScrollView style={[styles.scrollViewContainer, { backgroundColor: colors.pureWhite }]}>
<ScrollView style={[styles.scrollViewContainer, { backgroundColor: theme.colors.surface }]}>
<TouchableRipple onPress={handleProjectPress}>
<TextInput
dense={true}
@ -379,21 +436,8 @@ export default function DialogForm() {
/>
</TouchableRipple>
<TouchableRipple rippleColor={colors.pureWhite} onPress={handleOpenSheet}>
<TextInput
style={{ marginTop: 10 }}
dense={true}
editable={false}
value={shift ? shift : ''}
outlineColor={colors.amethystSmoke}
activeOutlineColor={colors.blue}
mode="outlined"
label='Shift'
placeholder='Shift'
right={<TextInput.Icon icon="chevron-down" />}
/>
</TouchableRipple>
<TouchableRipple rippleColor={colors.pureWhite} onPress={handleOpenSheetPosition}>
<TouchableRipple rippleColor={isDarkTheme ? theme.colors.blue : theme.colors.pureWhite} onPress={handleOpenSheetPosition}>
<TextInput
style={{ marginTop: 10 }}
dense={true}
@ -407,35 +451,54 @@ export default function DialogForm() {
right={<TextInput.Icon icon="chevron-down" />}
/>
</TouchableRipple>
<TouchableRipple rippleColor={colors.pureWhite} onPress={handleOpenSheetArea}>
<TextInput
style={{ marginTop: 10 }}
dense={true}
editable={false}
value={area ? area : ''}
outlineColor={colors.amethystSmoke}
activeOutlineColor={colors.blue}
mode="outlined"
label='Area'
placeholder='Area'
right={<TextInput.Icon icon="chevron-down" />}
/>
</TouchableRipple>
<TouchableRipple rippleColor={colors.pureWhite} onPress={handleOpenSheetPost}>
<TouchableRipple rippleColor={isDarkTheme ? theme.colors.blue : theme.colors.pureWhite} onPress={handleOpenSheet}>
<TextInput
style={{ marginTop: 10 }}
dense={true}
editable={false}
value={selectedPost ? selectedPost : ''}
value={shift ? shift : ''}
outlineColor={colors.amethystSmoke}
activeOutlineColor={colors.blue}
mode="outlined"
label='Pos'
placeholder='Pos'
label='Shift'
placeholder='Shift'
right={<TextInput.Icon icon="chevron-down" />}
/>
</TouchableRipple>
<TouchableRipple rippleColor={colors.pureWhite} onPress={showExpDatePickerFunc}>
{position !== 'spv' &&
<TouchableRipple rippleColor={isDarkTheme ? theme.colors.blue : theme.colors.pureWhite} onPress={handleOpenSheetArea}>
<TextInput
style={{ marginTop: 10 }}
dense={true}
editable={false}
value={area ? area : ''}
underlineColor={theme.colors.placeholder}
activeOutlineColor={theme.colors.blue}
mode="outlined"
label='Area'
placeholder='Area'
right={<TextInput.Icon icon="chevron-down" />}
/>
</TouchableRipple>
}
{position !== 'Danru' && position !== 'spv' &&
<TouchableRipple rippleColor={isDarkTheme ? theme.colors.blue : theme.colors.pureWhite} onPress={handleOpenSheetPost}>
<TextInput
style={{ marginTop: 10 }}
dense={true}
editable={false}
value={selectedPost ? selectedPost : ''}
outlineColor={colors.amethystSmoke}
activeOutlineColor={colors.blue}
mode="outlined"
label='Pos'
placeholder='Pos'
right={<TextInput.Icon icon="chevron-down" />}
/>
</TouchableRipple>
}
<TouchableRipple rippleColor={isDarkTheme ? theme.colors.blue : theme.colors.pureWhite} onPress={showExpDatePickerFunc}>
<TextInput
style={{ marginTop: 10 }}
dense={true}
@ -448,7 +511,7 @@ export default function DialogForm() {
placeholder='Masa Berlaku KTA'
/>
</TouchableRipple>
{renderImages}
{image !== null && renderImage}
</ScrollView>
<View style={{ width: '100%', marginTop: 10, marginBottom: 5 }}>
<Button icon="camera-plus" style={{ borderRadius: 10, backgroundColor: colors.semiBlue, paddingVertical: 5, marginHorizontal: 5, }} textColor={colors.blue} mode="contained-tonal" onPress={handleTakePicture}>
@ -457,11 +520,11 @@ export default function DialogForm() {
</View>
</View >
<View style={styles.buttonContainer}>
<Button mode="outlined" style={styles.button} textColor={colors.mistBlue} onPress={() => { navigation.goBack() }} >
<View style={[styles.buttonContainer, { backgroundColor: theme.colors.surface }]}>
<Button mode="outlined" style={styles.button} textColor={isDarkTheme ? theme.colors.blue : theme.colors.mistBlue} onPress={() => { navigation.goBack() }} >
Kembali
</Button>
<Button mode="contained" style={[styles.button, { backgroundColor: colors.blue }]} onPress={() => console.log('Handle Saved')}>
<Button mode="contained" style={[styles.button, { backgroundColor: colors.blue }]} textColor={theme.colors.pureWhite} onPress={handleSendRegisterData}>
Simpan
</Button>
</View>
@ -474,9 +537,14 @@ export default function DialogForm() {
snapPoints={['30%']}
bottomInset={10}
detached={true}
style={{ marginHorizontal: 15, shadowOpacity: 10 }}
style={{ marginHorizontal: 15, shadowOpacity: 10, }}
>
<BottomSheetView >
<BottomSheetView style={{
backgroundColor: isDarkTheme ? theme.colors.surface : theme.colors.pureWhite, position: "absolute", top: 0,
bottom: 0,
right: 0,
left: 0,
}}>
<TouchableRipple onPress={() => handleSelectPosition("Guard")}>
<List.Item
title="GUARD"
@ -504,9 +572,14 @@ export default function DialogForm() {
snapPoints={['30%']}
bottomInset={10}
detached={true}
style={{ marginHorizontal: 15, shadowOpacity: 10 }}
style={{ marginHorizontal: 15 }}
>
<BottomSheetView >
<BottomSheetView style={{
backgroundColor: isDarkTheme ? theme.colors.surface : theme.colors.pureWhite, position: "absolute", top: 0,
bottom: 0,
right: 0,
left: 0,
}}>
<TouchableRipple onPress={() => handleSelectShift("pagi")}>
<List.Item
title="PAGI"
@ -536,7 +609,12 @@ export default function DialogForm() {
detached={true}
style={{ marginHorizontal: 15 }}
>
<BottomSheetView >
<BottomSheetView style={{
backgroundColor: isDarkTheme ? theme.colors.surface : theme.colors.pureWhite, position: "absolute", top: 0,
bottom: 0,
right: 0,
left: 0,
}}>
{renderArea}
</BottomSheetView>
</BottomSheetModal>
@ -549,7 +627,12 @@ export default function DialogForm() {
detached={true}
style={{ marginHorizontal: 15 }}
>
<BottomSheetView >
<BottomSheetView style={{
backgroundColor: isDarkTheme ? theme.colors.surface : theme.colors.pureWhite, position: "absolute", top: 0,
bottom: 0,
right: 0,
left: 0,
}}>
{renderPost}
</BottomSheetView>
</BottomSheetModal>
@ -611,7 +694,6 @@ const styles = StyleSheet.create({
justifyContent: 'space-between',
padding: 10,
paddingBottom: 20,
backgroundColor: colors.pureWhite,
},
imageBlock: {
marginTop: 10,

Loading…
Cancel
Save