store dna all done

This commit is contained in:
CPM
2025-07-30 10:35:06 +05:30
commit b0399b39c6
157 changed files with 35444 additions and 0 deletions
+25
View File
@@ -0,0 +1,25 @@
import * as React from 'react';
import { useEffect } from 'react';
import { Provider } from 'react-redux';
import Routes from './navigation/Routes';
import { store } from './redux/store';
import { initTables } from './constants/database';
import { CreateImageFolders } from './constants/function';
import { GestureHandlerRootView } from 'react-native-gesture-handler';
function App() {
useEffect(() => {
initTables(); // Ensure DB tables are created
CreateImageFolders();
}, []);
return (
<GestureHandlerRootView style={{ flex: 1 }}>
<Provider store={store}>
<Routes />
</Provider>
</GestureHandlerRootView>
);
}
export default App;
+12
View File
@@ -0,0 +1,12 @@
export const BASE_URL = 'https://dax.parinaam.in/execute/dabur';
export const ApiURL = {
// login: `${BASE_URL}/api/v1/auth/login`,
pssscoreApi: `${BASE_URL}/mtd/pssscore`,
getotpApi: `https://api1.parinaam.in/api/dabur/SendOTP`,
verifyotpApi: `https://api1.parinaam.in/api/dabur/AuthenticateOTP`,
storeDNAfilter:`https://api1.parinaam.in/api/dabur/StoreDNAfilter`,
storeSearch :`https://api1.parinaam.in/api/dabur/StoreDNAstoreSearch`,
};
+158
View File
@@ -0,0 +1,158 @@
import AsyncStorage from '@react-native-async-storage/async-storage';
import axios from 'axios';
import { Alert } from 'react-native';
import { toastError } from '../components/Toast';
// Call this api function without token only for login
export const request = async (url, formData) => {
const header = {
'Content-Type': 'application/json',
};
return axios.post(url, formData, { headers: header })
.then(response => {
return response.data;
})
.catch(error => {
console.log(error);
return error;
});
};
// Call this api function with token (POST type)
// export const post = async (url: string, formData: any) => {
// const header = {
// Authorization: 'Bearer ',
// Accept: 'application/json',
// };
// return AsyncStorage.getItem('token').then(async value => {
// const loginToken = JSON.parse(value as any);
// console.log('loginToken----->',loginToken);
// header.Authorization = `Bearer ${loginToken}`;
// try {
// const response = await axios
// .post(url, formData, { headers: header });
// return response.data;
// } catch (error) {
// // console.log(url);
// // console.log(formData);
// console.log('AXIOS ERROR status: ', error);
// }
// });
// };
export const post = async (url, formData) => {
try {
// const tokenValue = await AsyncStorage.getItem('token');
// if (!tokenValue) {
// console.error('No token found in AsyncStorage');
// return null;
// }
// const loginToken = JSON.parse(tokenValue);
// console.log('loginToken----->', loginToken);
const headers = {
Authorization: ``,
Accept: 'application/json',
"X-API-Key":"f7fa9b09-ced8-4862-8cb7-5e7599d90fa2"
};
// console.log('URL => ',url);
// console.log('PARAMS => ',formData);
const response = await axios.post(url, formData, { headers });
return response.data;
} catch (error) {
console.error('API Error:', error.response?.data || error.message);
Alert.alert(`Alert`,`${error.response?.data?.message ?error.response?.data?.message:'Server Error'}`)
// toastError('Alert',`${error.response?.data?.message}`)
return null;
}
};
// CALL THIS API FUNCTION FOR GET TYPE API
export const get = async (url) => {
const header = {
Authorization: 'Bearer ',
Accept: 'application/json',
};
return AsyncStorage.getItem('token').then(async value => {
const loginToken = JSON.parse(value);
// console.log('URL---->',url);
// console.log('loginToken---->',loginToken);
header.Authorization = `Bearer ${loginToken}`;
try {
const response = await axios
.get(url, { headers: header });
return response.data;
} catch (error) {
if (error) {
console.log('AXIOS ERROR status: ', error);
}
}
});
};
export const uploadImage = async (url, formData) => {
const header = {
Authorization: 'Bearer ',
Accept: 'application/json',
'Content-Type': `multipart/form-data`,
};
return AsyncStorage.getItem('userToken').then(value => {
const token = JSON.parse(value);
header.Authorization = `Bearer ${token}`;
let dataf = {
uri: formData.path,
type: formData.mime,
name: formData.path,
};
var data = new FormData();
data.append('image', dataf);
console.log('header', header);
return fetch(url, {
headers: header,
method: 'POST',
body: data,
})
.then(response => response.json())
.then(json => {
console.log('API', url);
console.log('formData', formData);
console.log('json data', json);
return json;
})
.catch(error => {
console.log('API', url);
console.log('formData', formData);
console.log('error ', error);
});
});
};
export const cityData = async () => {
return AsyncStorage.getItem('cityshortname').then(e => {
const user = JSON.parse(e);
console.log('user,user', user);
return user;
});
};
Binary file not shown.

After

Width:  |  Height:  |  Size: 546 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 601 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 458 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 408 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 449 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 221 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

+102
View File
@@ -0,0 +1,102 @@
import React from 'react';
import {
Alert,
View,
TouchableOpacity,
Text,
} from 'react-native';
import CustomModal from './CustomModal';
import { useRoute } from '@react-navigation/native';
import { GlobalTheme } from '../theme';
export const CustomAlert = (
title = '',
msg = '',
onCancel = () => { },
onDone = () => { }
) => {
Alert.alert(title, msg, [
{
text: 'Cancel',
onPress: onCancel,
style: 'cancel',
},
{ text: 'OK', onPress: onDone },
]);
};
export function ConfirmSaveAlert({
showAlert = false,
onCancelCallBack = () => { },
onYesCallBack = () => { },
msg = 'Do you really want to save data?',
yesText = 'Yes',
cancelText = 'NO',
}) {
return (
<CustomModal
title={msg}
showModal={showAlert}
onClose={onCancelCallBack} // 👈 ensures modal closes when tapping close icon
>
<View style={{ flexDirection: 'row', justifyContent: 'space-between', marginTop: 20, width: '60%' }}>
<TouchableOpacity
style={{
paddingHorizontal: 25,
paddingVertical: 10,
backgroundColor: GlobalTheme.colors.primary,
borderRadius: 5,
}}
onPress={onYesCallBack}
>
<Text style={{
color: '#FFF',
fontSize: GlobalTheme.typography.fontSize.small,
fontWeight: GlobalTheme.typography.fontWeight.medium
}}>
{yesText}
</Text>
</TouchableOpacity>
<TouchableOpacity
style={{
paddingHorizontal: 25,
paddingVertical: 10,
backgroundColor: '#ccc',
borderRadius: 5,
}}
onPress={onCancelCallBack}
>
<Text style={{
color: '#000',
fontSize: GlobalTheme.typography.fontSize.small,
fontWeight: GlobalTheme.typography.fontWeight.medium
}}>
{cancelText}
</Text>
</TouchableOpacity>
</View>
</CustomModal>
);
}
export function ExitScreenAlert(props, callback) {
Alert.alert(
'',
'Do you really want to exit the screen?',
[
{
text: 'Cancel',
onPress: () => { },
style: 'cancel',
},
{
text: 'OK',
onPress: () => {
if (typeof callback === 'function') callback();
props.navigation.goBack();
},
},
]
);
}
+33
View File
@@ -0,0 +1,33 @@
// src/components/Background.js
import React from 'react';
import { StyleSheet, StatusBar, useColorScheme } from 'react-native';
import LinearGradient from 'react-native-linear-gradient';
import { SafeAreaView } from 'react-native-safe-area-context';
import { GlobalTheme } from '../theme';
const Background = ({ children, barcolor = 'light-content'}) => {
const isDarkMode = useColorScheme() === 'dark';
return (
<SafeAreaView style={styles.container}>
<StatusBar barStyle={'light-content'} />
<LinearGradient colors={['#E3EBF8', '#ffffff']} start={{ x: 0.5, y: 0 }} end={{ x: 0.5, y: 1 }} style={styles.gradient}>
{children}
</LinearGradient>
</SafeAreaView>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: GlobalTheme.colors.primary,
},
gradient: {
flex: 1,
// paddingHorizontal: 20,
},
});
export default Background;
+51
View File
@@ -0,0 +1,51 @@
import React, { useState } from 'react';
import { View, TouchableOpacity, Image, StyleSheet, Modal } from 'react-native';
import IMAGES from '../constants/Images';
import CustomCamera from './CustomCamera';
const CameraScreen = () => {
const [showCamera, setShowCamera] = useState(false);
const [imageUri, setImageUri] = useState(null);
const handleImageCaptured = (photo) => {
setImageUri(photo.uri);
setShowCamera(false);
};
return (
<View style={styles.container}>
<TouchableOpacity onPress={() => setShowCamera(true)}>
<Image
source={imageUri ? { uri: imageUri } : IMAGES.cameraIcon}
style={styles.imagePreview}
/>
</TouchableOpacity>
{/* Fullscreen Modal Camera */}
<Modal visible={showCamera} animationType="slide" presentationStyle="fullScreen">
<CustomCamera
onImageCaptured={handleImageCaptured}
onClose={() => setShowCamera(false)}
/>
</Modal>
</View>
);
};
export default CameraScreen;
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
imagePreview: {
width: 150,
height: 150,
borderRadius: 10,
borderWidth: 1,
borderColor: '#ccc',
},
});
+103
View File
@@ -0,0 +1,103 @@
import React from "react";
import {
View,
Text,
TouchableOpacity,
Modal,
StyleSheet,
} from "react-native";
import { GlobalTheme } from "../theme";
const CustomAlertModal = ({
showModal,
title,
message,
buttons = [],
style = {},
titleStyle = {},
messageStyle = {},
children,
}) => {
return (
<Modal
visible={showModal}
transparent
animationType="fade"
>
<View style={styles.backdrop}>
<View style={[styles.modalContainer, style]}>
<TouchableOpacity onPress={onClose} style={styles.closeIcon}>
<Text style={{ fontSize: 18 }}></Text>
</TouchableOpacity>
{title ? (
<Text style={[styles.title, titleStyle]}>{title}</Text>
) : null}
{message ? (
<Text style={[styles.message, messageStyle]}>{message}</Text>
) : null}
{children}
<View style={styles.buttonRow}>
{buttons.map((btn, index) => (
<TouchableOpacity
key={index}
style={[styles.button, { backgroundColor: btn.backgroundColor || GlobalTheme.colors.primary }]}
onPress={btn.onPress}
>
<Text style={[styles.buttonText, { color: btn.color || "#FFF" }]}>
{btn.label}
</Text>
</TouchableOpacity>
))}
</View>
</View>
</View>
</Modal>
);
};
export default CustomAlertModal;
const styles = StyleSheet.create({
backdrop: {
flex: 1,
backgroundColor: "rgba(0,0,0,0.5)",
justifyContent: "center",
alignItems: "center",
},
modalContainer: {
width: "80%",
backgroundColor: "#fff",
borderRadius: 10,
padding: 20,
elevation: 5,
},
title: {
fontSize: 18,
fontWeight: "bold",
marginBottom: 10,
textAlign: "center",
},
message: {
fontSize: 16,
marginBottom: 20,
textAlign: "center",
},
buttonRow: {
flexDirection: "row",
justifyContent: "flex-end",
marginTop: 10,
},
button: {
paddingHorizontal: 15,
paddingVertical: 8,
borderRadius: 5,
marginLeft: 10,
},
buttonText: {
fontSize: 14,
},
});
+13
View File
@@ -0,0 +1,13 @@
import { View, Text, TouchableOpacity } from 'react-native';
const CustomButton = ({ title, style, textstyle, onPress , disabled}) => {
return (
<TouchableOpacity disabled={disabled} onPress={onPress}>
<View style={{ marginHorizontal: 0, paddingVertical: 10, ...style }}>
<Text style={{ textAlign: 'center', ...textstyle }}>{title}</Text>
</View>
</TouchableOpacity>
);
};
export default CustomButton;
+173
View File
@@ -0,0 +1,173 @@
import React, { useState, useRef, useEffect } from 'react';
import { View, Text, TouchableOpacity, Image, StyleSheet, PermissionsAndroid, Platform } from 'react-native';
import { Camera, useCameraDevice } from 'react-native-vision-camera';
import { useIsFocused } from '@react-navigation/native';
const CustomCamera = ({ onImageCaptured, onClose }) => {
const cameraRef = useRef(null);
const [hasPermission, setHasPermission] = useState(false);
const [cameraPosition, setCameraPosition] = useState('back');
const [capturedPhoto, setCapturedPhoto] = useState(null);
const isFocused = useIsFocused();
const device = useCameraDevice(cameraPosition);
useEffect(() => {
(async () => {
const status = await Camera.requestCameraPermission();
console.log(status, "Camera permission status");
setHasPermission(status === 'granted');
})();
}, []);
const takePhoto = async () => {
if (cameraRef.current) {
try {
const photo = await cameraRef.current.takePhoto({
flash: 'off',
});
const photoUri = Platform.OS === 'android' ? `file://${photo.path}` : photo.path;
setCapturedPhoto(photoUri);
} catch (error) {
console.warn('Error taking photo:', error);
}
}
};
const flipCamera = () => {
setCameraPosition(prev => (prev === 'back' ? 'front' : 'back'));
};
const confirmPhoto = async () => {
if (onImageCaptured && capturedPhoto) {
try {
const imageInfo = await new Promise((resolve, reject) => {
Image.getSize(
capturedPhoto,
(width, height) => resolve({ width, height }),
(error) => reject(error)
);
});
const normalizedUri = Platform.OS === 'android' ? capturedPhoto : `file://${capturedPhoto}`;
onImageCaptured({
uri: normalizedUri,
width: imageInfo.width,
height: imageInfo.height,
storeData: {},
});
} catch (e) {
console.warn('Failed to get image size:', e);
onImageCaptured({
uri: capturedPhoto,
width: 0,
height: 0,
storeData: {},
});
}
}
};
const retakePhoto = () => {
setCapturedPhoto(null);
};
if (!device || !hasPermission) {
return (
<View style={styles.centered}>
<Text>Loading Camera...</Text>
</View>
);
}
return (
<View style={styles.container}>
{capturedPhoto ? (
<View style={styles.previewContainer}>
<Image source={{ uri: capturedPhoto }} style={styles.preview} resizeMode="contain" />
<View style={styles.buttonRow}>
<TouchableOpacity onPress={retakePhoto} style={styles.button}>
<Text>Retake</Text>
</TouchableOpacity>
<TouchableOpacity onPress={confirmPhoto} style={styles.button}>
<Text>Use Photo</Text>
</TouchableOpacity>
</View>
</View>
) : (
<>
{isFocused && !capturedPhoto && (
<Camera
style={StyleSheet.absoluteFill}
device={device}
isActive={true}
photo={true}
ref={cameraRef}
/>
)}
<View style={styles.controls}>
<TouchableOpacity onPress={flipCamera} style={styles.button}>
<Text>Flip</Text>
</TouchableOpacity>
<TouchableOpacity onPress={takePhoto} style={styles.captureButton} />
<TouchableOpacity onPress={onClose} style={styles.button}>
<Text>Close</Text>
</TouchableOpacity>
</View>
</>
)}
</View>
);
};
export default CustomCamera;
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: 'black',
height: '100%',
},
previewContainer: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
height: '100%',
},
preview: {
width: '100%',
height: '80%',
},
controls: {
position: 'absolute',
bottom: 30,
width: '100%',
flexDirection: 'row',
justifyContent: 'space-around',
alignItems: 'center',
},
captureButton: {
width: 70,
height: 70,
borderRadius: 35,
backgroundColor: 'white',
borderWidth: 5,
borderColor: 'gray',
},
button: {
backgroundColor: 'white',
padding: 10,
borderRadius: 8,
},
buttonRow: {
flexDirection: 'row',
justifyContent: 'space-around',
marginTop: 20,
width: '100%',
},
centered: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
});
+41
View File
@@ -0,0 +1,41 @@
// src/components/CustomDropdown.js
import React from 'react';
import { StyleSheet } from 'react-native';
import { Dropdown } from 'react-native-element-dropdown';
const CustomDropdown = ({
data = [],
value,
onChange,
placeholder = '--Select--',
containerStyle = {},
}) => {
return (
<Dropdown
style={[styles.dropdown, containerStyle]}
data={data}
labelField="label"
valueField="value"
placeholder={placeholder}
placeholderStyle={{ fontSize: 13, fontWeight: '500', color: 'gray' }}
value={value}
selectedTextStyle={{fontSize: 13, fontWeight: '500', color: '#000'}}
itemTextStyle={{fontSize:13,color:'#000'}}
onChange={onChange}
/>
);
};
const styles = StyleSheet.create({
dropdown: {
borderWidth: 1,
borderColor: '#ccc',
borderRadius: 8,
paddingHorizontal: 10,
height: 45,
marginBottom: 0, // ✅ no margin
paddingBottom: 0, // ✅ no padding
},
});
export default CustomDropdown;
+73
View File
@@ -0,0 +1,73 @@
import { StyleSheet, Text, View, Image, TouchableOpacity } from 'react-native'
import React from 'react'
import { GlobalTheme } from '../theme'
const CustomHeader = ({ title, leftIcon, rightIcon, onLeftPress, onRightPress }) => {
return (
<View style={styles.headerStyle}>
{leftIcon &&
<TouchableOpacity onPress={onLeftPress} style={styles.leftSection}>
{leftIcon && <Image source={leftIcon} style={styles.leftIconStyle} />}
</TouchableOpacity>
}
<View style={styles.titleContainer}>
<Text style={styles.headerText}>{title}</Text>
</View>
<TouchableOpacity onPress={onRightPress} style={styles.rightSection}>
{rightIcon && <Image source={rightIcon} style={styles.rightIconStyle} />}
</TouchableOpacity>
</View>
)
}
export default CustomHeader
const styles = StyleSheet.create({
headerStyle: {
height: '8%',
backgroundColor: '#113F8C',
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
paddingHorizontal: 15,
},
leftSection: {
backgroundColor: '#295398',
flexDirection: 'row',
padding: 8,
alignItems: 'center',
borderRadius: 20
// width: 70,
},
rightSection: {
alignItems: 'flex-end',
width: 40,
},
titleContainer: {
flex: 1,
alignItems: 'center',
},
headerText: {
color: GlobalTheme.colors.white,
fontSize: GlobalTheme.typography.fontSize.medium,
fontWeight: '600',
},
backText: {
marginLeft: 6,
color: GlobalTheme.colors.white,
fontSize: GlobalTheme.typography.fontSize.small,
},
leftIconStyle: {
height: 20,
width: 20,
resizeMode: 'contain',
},
rightIconStyle: {
height: 35,
width: 35,
resizeMode: 'contain',
},
})
+79
View File
@@ -0,0 +1,79 @@
import React from 'react';
import { View, Text, Modal, StyleSheet, TouchableOpacity, Image} from 'react-native';
import IMAGES from '../constants/Images';
const CustomModal = ({
showModal,
title,
message,
children,
// onClose,
style = {},
titleStyle = {},
messageStyle = {},
hideDefaultClose = false, // 🔥 new prop
onClose = null
}) => {
return (
<Modal visible={showModal} transparent animationType="fade">
<View style={styles.overlay}>
<View style={[styles.modalContainer, style]}>
{onClose && (
<TouchableOpacity onPress={onClose} style={styles.closeButton}>
<Image source={IMAGES.crossIcon} style={styles.iconStyle} />
</TouchableOpacity>
)}
{title && <Text style={[styles.title, titleStyle]}>{title}</Text>}
{message && <Text style={[styles.message, messageStyle]}>{message}</Text>}
<View style={{ marginVertical: 10 }}>{children}</View>
</View>
</View>
</Modal>
);
};
export default CustomModal;
const styles = StyleSheet.create({
overlay: {
flex: 1,
backgroundColor: '#00000088',
justifyContent: 'center',
alignItems: 'center',
},
modalContainer: {
width: '90%',
backgroundColor: '#fff',
borderRadius: 12,
padding: 15,
alignItems: 'center',
minHeight:200
},
title: {
fontSize: 18,
fontWeight: 'bold',
marginBottom: 6,
},
message: {
fontSize: 14,
color: '#555',
textAlign: 'center',
},
closeButton: {
alignSelf:'flex-end',
marginBottom:15
},
closeText: {
color: '#fff',
fontWeight: 'bold',
},
iconStyle:{
height:35,
width:35,
resizeMode:'contain',
// tintColor:'red'
}
});
+85
View File
@@ -0,0 +1,85 @@
import { View, Text, TextInput, StyleSheet, } from 'react-native';
import React, { useState } from 'react';
import { GlobalTheme, Screen } from '../theme';
const CustomTextInput = ({
label,
value,
onChangeText,
keyboardType,
secureTextEntry,
right,
textstyle,
viewstyle,
maxLength
}) => {
const [isFocused, setFocused] = useState(false);
const [hidepassword, setHidePassword] = useState(false);
const handleFocus = () => {
setFocused(true);
};
const handleBlur = () => {
setFocused(false);
};
const inputStyle = {
borderColor: isFocused ? '#2680EB' : '#DFDFDF',
borderWidth: isFocused ? 2 : 1,
backgroundColor: isFocused ? '#FFF' : '#Fff',
borderRadius: GlobalTheme.borderRadius.md,
};
return (
<View style={styles.inputContainer}>
<Text
style={{
color: GlobalTheme.colors.black,
fontSize: GlobalTheme.typography.fontSize.small,
fontWeight: GlobalTheme.typography.fontWeight.regular,
marginTop: 10,
marginHorizontal: 0,
...textstyle,
}}>
{label}
</Text>
<View
style={{
borderRadius: GlobalTheme.borderRadius.lgg,
// borderWidth: 6,
borderColor: isFocused ? '#DFECFF' : 'transparent',
marginTop: 8,
...viewstyle,
}}>
<TextInput
maxLength={maxLength}
style={[styles.input, inputStyle]}
value={value}
onChangeText={onChangeText}
keyboardType={keyboardType}
autoCapitalize="none"
onFocus={handleFocus}
onBlur={handleBlur}
placeholder={`Enter your ${label.toLowerCase()}`}
placeholderTextColor={'#555555'}
/>
</View>
</View>
);
};
export default CustomTextInput;
const styles = StyleSheet.create({
input: {
height: 50,
borderWidth: 1,
padding: 10,
color: GlobalTheme.colors.black,
},
inputContainer: {
marginBottom: 16,
},
});
+8
View File
@@ -0,0 +1,8 @@
// colors.js
export const colors = [
{ bgColor: '#eddeb8', textColor: '#0c8fa5' },
{ bgColor: '#dcf2ee', textColor: '#f39a19' },
{ bgColor: '#eebdc3', textColor: '#ff5f5f' },
{ bgColor: '#d1c4e9', textColor: '#6A1B9A' },
{ bgColor: '#ffe0b2', textColor: '#EF6C00' },
];
+75
View File
@@ -0,0 +1,75 @@
import React from 'react';
import AntDesign from 'react-native-vector-icons/AntDesign';
import Entypo from 'react-native-vector-icons/Entypo';
import EvilIcons from 'react-native-vector-icons/EvilIcons';
import Feather from 'react-native-vector-icons/Feather';
import FontAwesome from 'react-native-vector-icons/FontAwesome';
import Fontisto from 'react-native-vector-icons/Fontisto';
import Foundation from 'react-native-vector-icons/Foundation';
import Ionicons from 'react-native-vector-icons/Ionicons';
import MaterialIcons from 'react-native-vector-icons/MaterialIcons';
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
import Octicons from 'react-native-vector-icons/Octicons';
import Zocial from 'react-native-vector-icons/Zocial';
import SimpleLineIcons from 'react-native-vector-icons/SimpleLineIcons';
import { GlobalTheme } from '../theme';
function CustomIcon({
iconLibrary,
icon,
size = 22,
color = GlobalTheme.colors.primary,
style = {},
...rest
}) {
let Icon = AntDesign;
switch (iconLibrary) {
case 'AntDesign':
Icon = AntDesign;
break;
case 'Entypo':
Icon = Entypo;
break;
case 'EvilIcons':
Icon = EvilIcons;
break;
case 'Feather':
Icon = Feather;
break;
case 'FontAwesome':
Icon = FontAwesome;
break;
case 'Fontisto':
Icon = Fontisto;
break;
case 'Foundation':
Icon = Foundation;
break;
case 'Ionicons':
Icon = Ionicons;
break;
case 'MaterialIcons':
Icon = MaterialIcons;
break;
case 'MaterialCommunityIcons':
Icon = MaterialCommunityIcons;
break;
case 'Octicons':
Icon = Octicons;
break;
case 'Zocial':
Icon = Zocial;
break;
case 'SimpleLineIcons':
Icon = SimpleLineIcons;
break;
default:
Icon = AntDesign;
}
return <Icon name={icon} size={size} style={style} color={color} {...rest} />;
}
export default CustomIcon;
+32
View File
@@ -0,0 +1,32 @@
const IMAGES = {
// AppLogo: require('../assets/Images/logo.png'),
AppLogo: require('../assets/Images/logo.png'),
filterIcon: require('../assets/Icons/filter.png'),
menuIcon: require('../assets/Icons/menu.png'),
pluscircleIcon: require('../assets/Icons/pluscircle.png'),
rightArrowIcon: require('../assets/Icons/rightarrow.png'),
leftArrowIcon: require('../assets/Icons/leftarrow.png'),
crossIcon: require('../assets/Icons/cross.png'),
searchIcon: require('../assets/Icons/search.png'),
greenCameraIcon: require('../assets/Icons/green_camera.png'),
redCameraIcon: require('../assets/Icons/red_camera.png'),
normalCameraIcon: require('../assets/Icons/camera.png'),
WhiteBGIMG: require('../assets/Images/white.png'),
backIcon:require('../assets/Icons/backIcon.png'),
greenTick:require('../assets/Icons/greenTick.png'),
storeIcon:require('../assets/Icons/store.png'),
reportIcon:require('../assets/Icons/report.png'),
downIcon:require('../assets/Icons/down.png'),
upArrow:require('../assets/Icons/uparrow.png'),
footerImage : require('../assets/Images/footerImage.png'),
Logo: require('../assets/Images/applogo.png'),
WelcomeBackground: require('../assets/Images/welcomebackground.png'),
Welcomelogo: require('../assets/Images/welcomelogo.png'),
AuthTopBG: require('../assets/Images/logintopBG.png'),
AuthTopBGNew: require('../assets/Images/appLogoNew.png'),
AuthBottomBG: require('../assets/Images/loginbottomBG.png'),
dotsIcon: require('../assets/Icons/dots.png'),
};
export default IMAGES;
+54
View File
@@ -0,0 +1,54 @@
import React from 'react';
import { useWindowDimensions, Dimensions, View, Text, ActivityIndicator, StyleSheet, Image, StatusBar } from 'react-native';
import LoaderKit from 'react-native-loader-kit'
const Loader = ({ visible = false , loadingtext = 'Loading...'}) => {
const { width, height } = useWindowDimensions();
const height2 = height + 100
return (
visible && (
<View style={[style.container, { width }]}>
<View style={{ marginTop: 20,alignItems:'center' }}>
<LoaderKit
style={{ width: 70, height: 70 }}
name={'BallSpinFadeLoader'} // Optional: see list of animations below
color={'#fff'} // Optional: color can be: 'red', 'green',... or '#ddd', '#ffffff',...
/>
<Text style={{ color: '#fff', fontSize: 14, textAlign: 'center', marginTop: 10, fontWeight: '500' }}>{loadingtext}</Text>
</View>
</View>
)
);
};
const style = StyleSheet.create({
label: {
marginLeft: 10,
fontSize: 16,
color: 'white',
fontFamily: "FuturaPT-Book",
},
loader: {
height: 100,
backgroundColor: 'transparent',
marginHorizontal: 50,
borderRadius: 5,
flexDirection: 'column',
alignItems: 'center',
paddingHorizontal: 0,
},
container: {
top: 0,
bottom: 0,
position: 'absolute',
zIndex: 1050,
backgroundColor: 'rgba(0,0,0,0.5)',
justifyContent: 'center',
},
});
export default Loader;
+36
View File
@@ -0,0 +1,36 @@
// Toast.js
import Toast from 'react-native-toast-message';
const showToast = (type, text1, text2) => {
Toast.show({
type: type,
text1: text1,
text2: text2,
position: 'top',
visibilityTime: 3000,
autoHide: true,
topOffset: 30,
bottomOffset: 40,
text1Style: {
fontSize: 15, // Custom font size for the main text
fontWeight: 'bold',
},
text2Style: {
fontSize: 14, // Custom font size for the secondary text
color:'gray'
},
});
};
const ToastComponent = () => {
return <Toast innerRef={(ref) => Toast.setRef(ref)} />;
};
const toastSuccess = (text1, text2) => {
showToast('success', text1, text2);
};
const toastError = (text1, text2) => {
showToast('error', text1, text2);
};
export { ToastComponent, toastSuccess, toastError };
+9
View File
@@ -0,0 +1,9 @@
import * as RNFS from 'react-native-fs';
const common_ImagePath=`${RNFS.DocumentDirectoryPath}/`;
const ImageFolderPath=`${common_ImagePath}PerformicsAllImages/`;
const FeedbackImagesFolderPath=ImageFolderPath+`Store_DNA/`;
const defUploadFolder='BulkImages';
export {common_ImagePath ,FeedbackImagesFolderPath , ImageFolderPath ,defUploadFolder};
+177
View File
@@ -0,0 +1,177 @@
import SQLite from 'react-native-sqlite-storage';
const db = SQLite.openDatabase({ name: 'feedback.db', location: 'default' });
// Initialize FeedbackCategory table (full structure from mockDataFlat)
export const initTables = () => {
db.transaction(tx => {
// Table for API-fetched question/answer data
tx.executeSql(`
CREATE TABLE IF NOT EXISTS FeedbackCategory (
id INTEGER PRIMARY KEY AUTOINCREMENT,
MenuId INTEGER,
SurveyId INTEGER,
SurveyName TEXT,
CategoryId INTEGER,
Category TEXT,
CategorySequence INTEGER,
QuestionId INTEGER,
Question TEXT,
QuestionType TEXT,
QuestionTypeNew TEXT,
QEnable BOOLEAN,
QuestionImageAllow BOOLEAN,
QuestionImageMandatory BOOLEAN,
LengthValidation BOOLEAN,
MinLength INTEGER,
MaxLength INTEGER,
OTP BOOLEAN,
DateRange TEXT,
QuestionSequence INTEGER,
AnswerId INTEGER,
Answer TEXT,
AnswerSequence INTEGER,
ImageAllow1 BOOLEAN,
ImageAllow2 BOOLEAN,
Image1Mandatory BOOLEAN,
Image2Mandatory BOOLEAN,
EnableQuestion TEXT,
DisableQuestion TEXT,
ShowCat INTEGER,
SubCategoryId INTEGER,
SubCategory TEXT,
SubCategorySequence INTEGER,
CalFormula TEXT,
RefImage TEXT,
QuestionRefImage TEXT
);
`);
// Table for locally saved user responses
tx.executeSql(`
CREATE TABLE IF NOT EXISTS FeedBackLocalTable (
KEY_ID INTEGER PRIMARY KEY AUTOINCREMENT,
STORE_ID INTEGER,
VISIT_DATE NVARCHAR,
SURVEY_ID INTEGER,
CATEGORY_ID INTEGER,
SUB_CATEGORY_ID INTEGER,
QUESTION NVARCHAR,
QUESTION_ID INTEGER,
QUESTION_TYPE NVARCHAR,
ANSWER NVARCHAR,
ANSWER_ID INTEGER,
QuestionImageAllow INTEGER,
IMAGE_ALLOW1 INTEGER,
IMAGE1 NVARCHAR,
IMAGE_ALLOW2 INTEGER,
IMAGE2 NVARCHAR,
MULTI_OPTIONS_IDS NVARCHAR,
IS_DISABLED NVARCHAR,
IS_ENABLED NVARCHAR,
MENU_ID INTEGER,
ADDED_DATE TEXT,
VerifyOTP INTEGER,
QUESTION_REF_IMAGE TEXT,
STATUS NVARCHAR
);
`);
//
tx.executeSql(`
CREATE TABLE IF NOT EXISTS StoreInfoDNALocal (
KEY_ID INTEGER PRIMARY KEY AUTOINCREMENT,
VISIT_DATE NVARCHAR ,
StoreId INTEGER,
StoreName NVARCHAR,
ChainName NVARCHAR,
Address NVARCHAR,
Pincode NVARCHAR,
CityName NVARCHAR,
StateName NVARCHAR,
StoreType NVARCHAR,
UPLOAD_STATUS NVARCHAR
);
`);
});
};
// Bulk insert for FeedbackCategory (full schema)
export const bulkInsertData = (tableName, dataArray) => {
return new Promise((resolve, reject) => {
if (!Array.isArray(dataArray) || dataArray.length === 0) {
resolve(true);
return;
}
if (tableName !== 'FeedbackCategory') {
reject(`Unsupported table: ${tableName}`);
return;
}
const escape = str => (str || '').toString().replace(/'/g, "''");
const values = dataArray.map(item => `(
'${item.MenuId}', '${item.SurveyId}', '${escape(item.SurveyName)}',
'${item.CategoryId}', '${escape(item.Category)}', '${item.CategorySequence}',
'${item.QuestionId}', '${escape(item.Question)}', '${escape(item.QuestionType)}',
'${escape(item.QuestionTypeNew)}', '${item.QEnable ? 1 : 0}', '${item.QuestionImageAllow ? 1 : 0}',
'${item.QuestionImageMandatory ? 1 : 0}', '${item.LengthValidation ? 1 : 0}', '${item.MinLength}',
'${item.MaxLength}', '${item.OTP ? 1 : 0}', '${escape(item.DateRange)}', '${item.QuestionSequence}',
'${item.AnswerId}', '${escape(item.Answer)}', '${item.AnswerSequence}',
'${item.ImageAllow1 ? 1 : 0}', '${item.ImageAllow2 ? 1 : 0}', '${item.Image1Mandatory ? 1 : 0}',
'${item.Image2Mandatory ? 1 : 0}', '${escape(item.EnableQuestion)}', '${escape(item.DisableQuestion)}',
'${item.ShowCat}', '${item.SubCategoryId}', '${escape(item.SubCategory)}', '${item.SubCategorySequence}',
'${escape(item.CalFormula)}', '${escape(item.RefImage)}', '${escape(item.QuestionRefImage)}'
)`);
const sql = `
INSERT INTO FeedbackCategory (
MenuId, SurveyId, SurveyName, CategoryId, Category, CategorySequence,
QuestionId, Question, QuestionType, QuestionTypeNew, QEnable, QuestionImageAllow,
QuestionImageMandatory, LengthValidation, MinLength, MaxLength, OTP, DateRange,
QuestionSequence, AnswerId, Answer, AnswerSequence, ImageAllow1, ImageAllow2,
Image1Mandatory, Image2Mandatory, EnableQuestion, DisableQuestion,
ShowCat, SubCategoryId, SubCategory, SubCategorySequence,
CalFormula, RefImage, QuestionRefImage
)
VALUES ${values.join(',')}
`;
db.transaction(tx => {
tx.executeSql(`DELETE FROM FeedbackCategory`, [], () => {
tx.executeSql(sql, [], () => {
console.log(`${tableName} inserted successfully`);
resolve(true);
}, (e1, err) => {
console.log('Insert error:', err);
reject(err);
});
}, (err) => {
console.log('Delete error:', err);
reject(err);
});
});
});
};
// Generic SELECT
export const getAllFromTable = (tableName) => {
return new Promise((resolve, reject) => {
db.transaction(tx => {
tx.executeSql(`SELECT * FROM ${tableName}`, [], (tx, results) => {
const rows = [];
for (let i = 0; i < results.rows.length; i++) {
rows.push(results.rows.item(i));
}
resolve(rows);
}, (err) => {
console.log(`Select error on ${tableName}:`, err);
reject(err);
});
});
});
};
export default db;
+455
View File
@@ -0,0 +1,455 @@
import { defUploadFolder, FeedbackImagesFolderPath, ImageFolderPath } from "./constant";
import moment from 'moment';
import { Image } from "react-native";
import * as RNFS from 'react-native-fs';
import PhotoManipulator from 'react-native-photo-manipulator';
import ImageResizer from '@bam.tech/react-native-image-resizer';
import { toastError } from "./Toast";
import IMAGES from "./Images";
export async function CreateImageFolders() {
console.log('CreateDirKPIimg:');
await RNFS.mkdir(ImageFolderPath);
await RNFS.mkdir(FeedbackImagesFolderPath);
}
export async function getImage(imgdata , userId , item) {
console.log("item===", item);
if (typeof imgdata == 'object' && imgdata.errorCode === 'camera_unavailable') {
console.log("camera not available");
return null;
}
if (typeof imgdata == 'object' && imgdata.uri != null) {
try {
const imgurl = imgdata.uri.startsWith('file://') ? imgdata.uri : `file://${imgdata.uri}`;
// 🔹 Get dynamic image width and height
const { width: imgWidth, height: imgHeight } = await new Promise((resolve, reject) => {
Image.getSize(imgurl, (width, height) => resolve({ width, height }), reject);
});
console.log("Image size ===", imgWidth, imgHeight);
const picture_clickedd = new Date();
const picture_clicked_time = moment(picture_clickedd).format('MM-DD-YYYY HH:mm:ss');
const picture_clicked_time_for_pic_name = moment(picture_clickedd).format('MMDDYYYY_HHmmss');
const filename = userId + picture_clicked_time_for_pic_name + '.jpg';
const mark_text1 = picture_clicked_time;
const mark_text2 = `User Id: ${userId} | StoreName: ${item?.StoreName} | StoreId: ${item?.StoreId} | Date: ${picture_clicked_time}`;
const imagePath = `${FeedbackImagesFolderPath}${filename}`;
console.log("Copying image to:", imagePath);
await RNFS.copyFile(imgurl.replace('file://', ''), imagePath);
const restore_data = {
destFilePath: imagePath,
imgurl: imagePath,
imgWidth,
imgHeight,
mark_text1,
mark_text2,
};
const is_restored = await restore_ImageWithMetaData(restore_data, imgdata);
console.log("Image restored:", is_restored);
console.log("Image imagePath:", imagePath);
return { finalPath: imagePath };
} catch (err) {
console.log('❌ ERROR in getImage:', err);
return null;
}
}
return null;
}
// const waterMarkText = async (text = '', imgurl = '', text2 = '', imgWidth = 0, imgHeight = 0) => {
// const wrapText = (text2, maxCharsPerLine) => {
// const words = text2.split(' ');
// const lines = [];
// let currentLine = '';
// for (let word of words) {
// if ((currentLine + word).length <= maxCharsPerLine) {
// currentLine += (currentLine ? ' ' : '') + word;
// } else {
// lines.push(currentLine);
// currentLine = word;
// }
// }
// console.log('currentLine====>', currentLine);
// if (currentLine) lines.push(currentLine);
// return lines;
// };
// try {
// if (!imgurl) throw new Error("Image URL is missing");
// const cleanPath = imgurl.startsWith('file://') ? imgurl : `file://${imgurl}`;
// const stripHeight = 250;
// // ✅ Copy from Android assets folder to filesystem
// const destPath = `${RNFS.CachesDirectoryPath}/white.png`;
// const exists = await RNFS.exists(destPath);
// if (!exists) {
// await RNFS.copyFileAssets('white.png', destPath); // white.png is in android/app/src/main/assets
// }
// const stripFilePath = `file://${destPath}`;
// // ✅ Step 2: Overlay strip
// const imageWithStrip = await PhotoManipulator.overlayImage(
// cleanPath,
// stripFilePath,
// { x: 0, y: imgHeight - stripHeight },
// 'image/jpeg'
// );
// // ✅ Step 3: Add text
// const lines = wrapText(text2, 80); // You can adjust 25 depending on font size / image width
// const texts = [
// {
// position: { x: 30, y: 50 },
// text: text,
// textSize: 80,
// color: '#FF0000',
// },
// ...lines.map((line, i) => ({
// position: { x: 30, y: imgHeight - 120 + i * 90 }, // 90 = line height
// text: line,
// textSize: 80,
// color: '#000000',
// })),
// ];
// const finalImagePath = await PhotoManipulator.printText(imageWithStrip, texts, 'image/jpeg');
// const uri = finalImagePath.startsWith('file://') ? finalImagePath : `file://${finalImagePath}`;
// return { success: true, uri };
// } catch (error) {
// console.error("❌ Error in waterMarkText:", error);
// return { success: false, error: error.message };
// }
// };
const waterMarkText = async (text = '', imgurl = '', text2 = '', imgWidth = 0, imgHeight = 0) => {
const wrapTextByImageWidth = (text, imgWidth, fontSize = 80, padding = 60) => {
const avgCharWidth = fontSize * 0.6; // Conservative estimate
const maxChars = Math.floor((imgWidth - padding) / avgCharWidth);
const words = text.split(' ');
const lines = [];
let currentLine = '';
for (let word of words) {
if ((currentLine + ' ' + word).trim().length <= maxChars) {
currentLine += (currentLine ? ' ' : '') + word;
} else {
lines.push(currentLine);
currentLine = word;
}
}
if (currentLine) lines.push(currentLine);
return lines;
};
try {
if (!imgurl) throw new Error("Image URL is missing");
const cleanPath = imgurl.startsWith('file://') ? imgurl : `file://${imgurl}`;
const stripHeight = 250;
// ✅ White strip setup
const destPath = `${RNFS.CachesDirectoryPath}/white.png`;
const exists = await RNFS.exists(destPath);
if (!exists) {
await RNFS.copyFileAssets('white.png', destPath);
}
const stripFilePath = `file://${destPath}`;
// ✅ Overlay strip at bottom
const imageWithStrip = await PhotoManipulator.overlayImage(
cleanPath,
stripFilePath,
{ x: 0, y: imgHeight - stripHeight },
'image/jpeg'
);
// ✅ Wrap text2 according to image width
const fontSize = 80;
const lineHeight = 90;
const padding = 60;
const lines = wrapTextByImageWidth(text2, imgWidth, fontSize, padding);
const startY = imgHeight - stripHeight + 40; // Add top padding inside strip
// ✅ Build text objects
const texts = [
{
position: { x: 30, y: 50 },
text: text,
textSize: fontSize,
color: '#FF0000',
},
...lines.map((line, index) => ({
position: { x: 30, y: startY + index * lineHeight },
text: line,
textSize: fontSize,
color: '#000000',
})),
];
const finalImagePath = await PhotoManipulator.printText(imageWithStrip, texts, 'image/jpeg');
const uri = finalImagePath.startsWith('file://') ? finalImagePath : `file://${finalImagePath}`;
return { success: true, uri };
} catch (error) {
console.error("❌ Error in waterMarkText:", error);
return { success: false, error: error.message };
}
};
export async function restore_ImageWithMetaData(data, imgdata) {
let { destFilePath, imgurl, imgWidth, imgHeight, mark_text1, mark_text2 } = data;
try {
const cleanImgUrl = imgurl.startsWith('file://') ? imgurl : `file://${imgurl}`;
const rawPath = cleanImgUrl.replace('file://', '');
// Fallback to imgdata values
if (!imgWidth && imgdata?.width) imgWidth = imgdata.width;
if (!imgHeight && imgdata?.height) imgHeight = imgdata.height;
if (!imgWidth || !imgHeight || !rawPath) {
console.log('❌ Invalid resize parameters', { imgWidth, imgHeight, imgurl });
return false;
}
const fileExists = await RNFS.exists(rawPath);
if (!fileExists) {
console.log('❌ Image file not found at:', rawPath);
return false;
}
// Step 1: Resize (only if dimensions are large)
const resized = await resizeImage(cleanImgUrl, imgWidth, imgHeight);
if (!resized.success) {
console.log("❌ Resize failed:", resized.error);
return false;
}
const resizedUrl = resized.uri;
const resizedData = resized.imgData;
const resizedWidth = parseInt(resizedData.width);
const resizedHeight = parseInt(resizedData.height);
// Step 2: Watermark
const marked = await waterMarkText(mark_text1, resizedUrl, mark_text2, resizedWidth, resizedHeight);
if (!marked.success || !marked.uri) {
console.log("❌ Watermark failed:", marked.error);
return false;
}
const markedUrl = marked.uri;
// Step 3: Reduce pixels (if needed)
const reduced = await reducePixels(markedUrl, resizedWidth, resizedHeight);
if (!reduced.success) {
console.error("❌ Pixel reduction failed:", reduced.error);
return false;
}
const finalUri = reduced.uri;
// Step 4: Copy to destination
const exists = await RNFS.exists(destFilePath);
if (exists) await RNFS.unlink(destFilePath);
await RNFS.copyFile(finalUri, destFilePath);
console.log("✅ Image with metadata saved at:", destFilePath);
// Step 5: DO NOT delete destFilePath — it's the one used in UI!
// Optional: Clean up temp files
const filesToDelete = [rawPath, resizedUrl, markedUrl]
.map(f => f.replace('file://', ''))
.filter(f => f !== destFilePath); // protect final image
await Promise.all(
filesToDelete.map(f => RNFS.unlink(f).catch(() => {}))
);
return true;
} catch (err) {
console.log("❌ restore_ImageWithMetaData failed:", err);
return false;
}
}
export function bytesToSize(bytes) {
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
if (bytes === 0) return '0 Byte';
const i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024)));
return Math.round(bytes / Math.pow(1024, i), 2) + ' ' + sizes[i];
}
export const resizeImage = async (imgurl = '', imgWidth, imgHeight) => {
if (!imgurl || !imgWidth || !imgHeight || isNaN(imgWidth) || isNaN(imgHeight)) {
console.log("❌ resizeImage: Invalid parameters", { imgurl, imgWidth, imgHeight });
return { success: false, error: 'Invalid resize parameters' };
}
try {
const response = await ImageResizer.createResizedImage(
imgurl, imgWidth, imgHeight, 'JPEG', 100, 0, null, false, { onlyScaleDown: true }
);
let size = bytesToSize(response.size);
console.log('✅ resizeImage: reduced filesize:', size);
return { success: true, uri: response.uri, imgData: response };
} catch (err) {
console.log("❌ resizeImage error:", err);
return { success: false, error: err };
}
};
export const reducePixels = async (imgurl = '', imgWidth, imgHeight) => {
if (!imgurl || !imgWidth || !imgHeight || isNaN(imgWidth) || isNaN(imgHeight)) {
console.log("❌ reducePixels: Invalid parameters", { imgurl, imgWidth, imgHeight });
return { success: false, error: 'Invalid pixel reduction parameters' };
}
// Resize only if image is large
if (imgWidth > 1100 || imgHeight > 1100) {
try {
const response = await ImageResizer.createResizedImage(
imgurl, 1100, 1100, 'JPEG', 40, 0, null, false,
{ onlyScaleDown: true, mode: 'contain' }
);
let size = bytesToSize(response.size);
console.log('✅ reducePixels: reduced size to', size, '→', response.width, 'x', response.height);
return { success: true, uri: response.uri };
} catch (err) {
console.log("❌ reducePixels error:", err);
return { success: false, error: err };
}
}
// No resizing needed
return { success: true, uri: imgurl };
};
// end image resize and reduce pixel function
export async function getAllFileForAFolder(path, Up_Foldername, KPIName = '') {
let subFolPath = path;
const ImgFiles = await RNFS.readDir(subFolPath);
Up_Foldername = Up_Foldername != null && Up_Foldername != '' ? Up_Foldername : defUploadFolder;
console.log("ImgFiles in ", subFolPath, ' are:', ImgFiles.length);
let allKPIfiles = [];
return new Promise.all(
ImgFiles.map(async sfile => {
if (sfile.isFile() && (sfile.name.includes('jpg') || sfile.name.includes('jpeg') || sfile.name.includes('png'))) {
let file = {
uri: sfile.path,
type: 'image/jpeg',
name: sfile.name,
filetype: 'image',
folderName: Up_Foldername,
}
allKPIfiles.push(file);
return file;
}
else if (sfile.isDirectory() && sfile.name == 'Recordings') {
let recordingsPath = sfile.path;
let recordingFiles = await RNFS.readDir(recordingsPath);
let VoiceUp_FolderPath = KPIName != null && KPIName != '' ? FolderForREC[KPIName] : defUploadFolder;
return new Promise.all(
recordingFiles.map(async rfile => {
if (rfile.isFile() && (rfile.name.includes('m4a') || rfile.name.includes('mp3'))) {
let file = {
uri: rfile.path,
type: (Platform.OS == 'ios' ? 'audio/m4a' : 'audio/mp3'),
name: rfile.name,
filetype: 'audio',
folderName: VoiceUp_FolderPath,
}
allKPIfiles.push(file);
return file;
}
else {
return '';
}
})
).then((val2) => {
return val2;
});
}
else {
return '';
}
})
).then((val1) => {
console.log('getAllFileForAFolder', val1);
return allKPIfiles;
}).catch((err) => {
console.log('error in getAllFileForAFolder', err);
return allKPIfiles;
})
}
/// download
export async function getDownloadJson(url, indata) {
let postdata = {
Downloadtype: indata.Downloadtype,
Username: indata.username,
Param1: '',
Param2: '',
};
console.log(postdata);
return await fetch(url, {
method: 'post',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify(postdata),
})
.then(response => {
return response.json();
})
.then((res) => {
// console.log('res',res);
let resd = JSON.parse(res);
return resd;
})
.catch(err => {
console.log('getdashboard error', err);
return { success: false };
});
}
+65
View File
@@ -0,0 +1,65 @@
export async function UploadImagesWithoutWait(postData,url){
console.log(url,postData);
return await UploadFormData(url,postData)
.then((res)=>{
console.log('test:',res);
if(typeof res=='object' && res.error!=null){
return false;
}
else if(res.includes('Success')){
console.log('image uploaded');
return true;
}
return false;
})
.catch((err)=>{
console.log(err);
return false;
});
}
export async function UploadData2(url, indata) {
return await fetch(url, {
method: 'post',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify(indata),
})
.then(response => {
return response.json();
})
.then((res) => {
return res;
})
.catch(err => {
console.log('UploadData error', err);
return { success: false, error: true, errorMsg: err };
});
}
export async function UploadFormData(url, formData) {
// console.log(url,formData._parts);
return await fetch(url, {
method: 'post',
headers: new Headers({ 'Content-Type': 'multipart/form-data', }),
body: formData,
})
.then(response => {
console.log('UploadData error3', response);
return response.text();
})
.then((res) => {
console.log('UploadData error1', res);
return res;
})
.catch(err => {
console.log('UploadData error2', err);
return { success: false, error: err };
});
}
+59
View File
@@ -0,0 +1,59 @@
import { toastError } from "./Toast";
export function validateNumber(val, type = '', showMsg = true) {
console.log(val, "type=======", type);
let isValid = true;
if (type.toLowerCase() == 'numeric' && val != '') {
let regex = new RegExp(/^\d+$/);
let isNUmeric = regex.test(val);
if (!isNUmeric) {
isValid = false;
if (showMsg) toastError('Alert', 'Please enter whole numbers only');
}
}
else if (type.toLowerCase() == 'decimal' && val != '') {
let regex = new RegExp(/^\d*\.?\d*$/);
let isNUmeric = regex.test(val);
if (!isNUmeric) {
isValid = false;
if (showMsg) toastError('Alert', 'Please enter decimal numbers only');
}
}
else if (type.toLowerCase() == 'text' && val != '') {
let regex = new RegExp(/^[a-zA-Z0-9@\s_.-]*$/);
let isNUmeric = regex.test(val);
if (!isNUmeric) {
isValid = false;
if (showMsg) toastError('Alert', 'Please enter only characters and digits');
}
}
else if (type.toLowerCase() === 'onlytext' && val !== '') {
let regex = /^[a-zA-Z\s]*$/; // ✅ allows only alphabets and space
const isAlpha = regex.test(val);
if (!isAlpha) {
isValid = false;
if (showMsg) toastError('Alert', 'Please enter only letters and spaces');
}
}
else if (type.toLowerCase() == 'textspc' && val != '') {
let regex = new RegExp(/^[a-zA-Z0-9\/-]*$/);
let isNUmeric = regex.test(val);
if (!isNUmeric) {
isValid = false;
if (showMsg) toastError('Alert', 'Please enter only characters and digits');
}
} else if (type.toLowerCase() == 'alphanumeric' && val != '') {
let regex = new RegExp(/^[a-zA-Z0-9]+$/);
let isNUmeric = regex.test(val);
if (!isNUmeric) {
isValid = false;
if (showMsg) toastError('Alert', 'Please enter only characters and digits');
}
}
return isValid;
}
+47
View File
@@ -0,0 +1,47 @@
import React from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import Splash from '../screens/AuthScreen/Splash';
import Login from '../screens/AuthScreen/Login';
import VerifyOTP from '../screens/AuthScreen/VerifyOTP';
import { ToastComponent } from '../constants/Toast';
import StoreInfo from '../screens/MainScreen/StoreInfo';
import Feedback from '../screens/MainScreen/Feedback';
import Dashboard from '../screens/MainScreen/Dashboard';
import FeedbackCategories from '../screens/MainScreen/Feedback/FeedbackCategories';
import { Platform, StatusBar, View } from 'react-native';
import Welcome from '../screens/MainScreen/WelcomePage';
const Stack = createNativeStackNavigator();
const Routes = () => {
const STATUSBAR_HEIGHT = Platform.OS === 'ios' ? 50 : StatusBar.currentHeight;
return (
<NavigationContainer>
{Platform.OS === 'ios' && (
<View style={{
height: STATUSBAR_HEIGHT,
bbarStyle:'light-content',
backgroundColor: '#113F8C',
}} />
)}
<StatusBar barStyle={'light-content'} backgroundColor={'#113F8C'} />
<Stack.Navigator
screenOptions={{ headerShown: false }}
initialRouteName="Splash">
<Stack.Screen name="Splash" component={Splash} />
<Stack.Screen name="Login" component={Login} />
<Stack.Screen name="VerifyOTP" component={VerifyOTP} />
<Stack.Screen name="Welcome" component={Welcome} />
<Stack.Screen name="StoreInfo" component={StoreInfo} />
<Stack.Screen name="Dashboard" component={Dashboard} />
<Stack.Screen name="Feedback" component={Feedback} />
<Stack.Screen name="FeedbackCategories" component={FeedbackCategories} />
</Stack.Navigator>
<ToastComponent />
</NavigationContainer>
);
};
export default Routes;
+5
View File
@@ -0,0 +1,5 @@
import userSlice from '../slices/userSlice';
export default {
user: userSlice,
};
+33
View File
@@ -0,0 +1,33 @@
// // src/redux/slices/authSlice.js
// import { createSlice } from '@reduxjs/toolkit';
// const initialState = {
// token: null,
// loading: false,
// error: null,
// };
// const authSlice = createSlice({
// name: 'auth',
// initialState,
// reducers: {
// loginStart: (state) => {
// state.loading = true;
// state.error = null;
// },
// loginSuccess: (state, action) => {
// state.loading = false;
// state.token = action.payload;
// },
// loginFailure: (state, action) => {
// state.loading = false;
// state.error = action.payload;
// },
// logout: (state) => {
// state.token = null;
// },
// },
// });
// export const { loginStart, loginSuccess, loginFailure, logout } = authSlice.actions;
// export default authSlice.reducer;
+25
View File
@@ -0,0 +1,25 @@
import {createSlice} from '@reduxjs/toolkit';
const initialState = {
token: '',
};
const userSlice = createSlice({
name: 'user',
initialState,
reducers: {
setUser(state, action) {
return {
...state,
...action.payload,
token: action?.payload?.usertoken,
};
},
resetUserState() {
return initialState;
},
},
});
export const {setUser, resetUserState} = userSlice.actions;
export default userSlice.reducer;
+5
View File
@@ -0,0 +1,5 @@
import { configureStore } from '@reduxjs/toolkit'
import reducer from './reducer'
export const store = configureStore({ reducer: reducer })
+98
View File
@@ -0,0 +1,98 @@
import React, { useEffect, useState } from 'react';
import { View, Text, Image, Platform, ImageBackground } from 'react-native';
import { KeyboardAwareScrollView } from 'react-native-keyboard-aware-scroll-view';
import IMAGES from '../../../constants/Images';
import { styles } from './style';
import { useDispatch } from 'react-redux';
import CustomTextInput from '../../../components/CustomTextInput';
import CustomButton from '../../../components/CustomButton';
import Loader from '../../../constants/Loader';
import { ApiURL } from '../../../api/ApiConstant';
import { toastError, toastSuccess } from '../../../constants/Toast';
import axios from 'axios';
import { SafeAreaView } from 'react-native-safe-area-context';
import { GlobalTheme } from '../../../theme';
import Geolocation from '@react-native-community/geolocation';
const Login = ({ navigation }) => {
const [loading, setLoading] = useState(false);
const dispatch = useDispatch();
const [username, setUsername] = useState('testah');
// geo loc
useEffect(() => {
Geolocation.getCurrentPosition(info => console.log("Location infoooo====>", JSON.stringify(info)));
}, [])
// end geo loc
const onSubmit = () => {
setLoading(true);
getOTP();
setTimeout(() => {
setLoading(false);
}, 100);
};
const getOTP = async () => {
try {
const params = {
"UserId": username
};
const config = {
method: 'post',
url: ApiURL.getotpApi,
headers: {
'api_key': '9a1f056fecb84eaf8eb4152dda22ab0501955c4f9bbe7daa8780740459fdde7a',
'Content-Type': 'application/json'
},
data: params
};
const response = await axios.request(config);
const res = response.data || [];
console.log('OTP is ===> ', res?.SendOTP);
if (res?.SendOTP[0].OTP === '0' || res?.SendOTP[0].OTP === 0) {
toastError("Alert", "Invalid User");
} else {
toastSuccess("Alert", res?.SendOTP[0]?.Messages);
navigation.navigate('VerifyOTP', { username: username });
}
// console.log('getotpApi res==>', JSON.stringify(res?.SendOTP[0]));
} catch (error) {
console.log("❌ OTP API error:", error);
}
};
return (
<SafeAreaView style={styles.container}>
<KeyboardAwareScrollView keyboardShouldPersistTaps="handled" enableOnAndroid={true} contentContainerStyle={{ justifyContent: 'center', flexGrow: 1, marginTop: 0 }} style={styles.container}>
<View style={{ backgroundColor: GlobalTheme.colors.primary, height: 40 }} />
<Image style={styles.appLogo} source={IMAGES.AuthTopBGNew} resizeMode='contain' />
<View style={styles.card}>
<Text style={styles.loginTitle}>Login</Text>
<CustomTextInput
label="Username"
placeholder="Enter username"
value={username}
onChangeText={setUsername}
containerStyle={styles.inputWrapper}
/>
<CustomButton onPress={() => onSubmit()} title={'Continue'} style={styles.btnbg} textstyle={styles.btntext} />
<ImageBackground source={IMAGES.AuthBottomBG} style={styles.AuthBottomBG} >
<Text style={{ position: 'absolute', textAlign: 'center', alignContent: 'center', alignSelf: 'center', bottom: 0, color: GlobalTheme.colors.gray }}> Copyright CPM India - 2025</Text>
{/* <Image source={IMAGES.Logo} style={styles.footerImage} resizeMode="contain"/> */}
</ImageBackground>
</View>
</KeyboardAwareScrollView>
<Loader visible={loading} loadingtext={'Loading ...'} />
</SafeAreaView>
);
};
export default Login;
+82
View File
@@ -0,0 +1,82 @@
import { StyleSheet, Dimensions } from 'react-native';
import { GlobalTheme } from '../../../theme';
import { normalize } from '../../../utilis/responsive';
const { width, height } = Dimensions.get('window');
export const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: GlobalTheme.colors.primary,
},
logoContainer: {
width: '100%',
},
appLogo: {
height: normalize(170),
width: '100%',
resizeMode: 'contain',
},
titleContainer: {
alignSelf: 'center',
marginBottom: normalize(10),
},
titleText: {
fontSize: normalize(20),
fontWeight: 'bold',
color: GlobalTheme.colors.black,
},
card: {
backgroundColor: GlobalTheme.colors.white,
borderTopLeftRadius: GlobalTheme.borderRadius.xxlg || normalize(20),
borderTopRightRadius: GlobalTheme.borderRadius.xxlg || normalize(20),
padding: normalize(20),
width: width,
alignSelf: 'center',
// marginTop: normalize(30),
shadowColor: '#000',
shadowOpacity: 0.05,
shadowOffset: { width: 0, height: 4 },
shadowRadius: 8,
elevation: 4,
flex: 1,
},
btnbg: {
backgroundColor: GlobalTheme.colors.secondary,
borderRadius: GlobalTheme.borderRadius.md,
marginTop: normalize(30),
paddingVertical: normalize(12),
alignItems: 'center',
},
btntext: {
color: GlobalTheme.colors.white,
fontSize: normalize(GlobalTheme.typography.fontSize.small),
fontWeight: GlobalTheme.typography.fontWeight.regular,
},
footer: {
marginTop: normalize(60),
alignItems: 'center',
},
footerImage: {
width: '100%',
height: normalize(80),
resizeMode: 'contain',
marginTop : normalize(160),
},
AuthBottomBG: {
width: '100%',
resizeMode: 'contain',
height: normalize(300),
justifyContent: 'center',
},
loginTitle: {
color: GlobalTheme.colors.black,
fontSize: normalize(GlobalTheme.typography.fontSize.large),
fontWeight: GlobalTheme.typography.fontWeight.medium,
textAlign: 'center',
marginBottom: normalize(20),
},
mainContainer: {
flex: 1,
backgroundColor: '#EAF0FF',
},
});
+95
View File
@@ -0,0 +1,95 @@
import { View, Text, Image, Dimensions, StyleSheet } from 'react-native';
import React, { useEffect } from 'react';
import IMAGES from '../../../constants/Images';
import AsyncStorage from '@react-native-async-storage/async-storage';
import { useDispatch } from 'react-redux';
import { setUser } from '../../../redux/slices/userSlice';
const SplashScreen = ({ navigation }) => {
const dispatch = useDispatch();
useEffect(() => {
const checkLoginStatus = async () => {
try {
const isuserlogin = await AsyncStorage.getItem('@Dabur_DNA_User');
console.log("isuserlogin", isuserlogin)
const parsedUser = JSON.parse(isuserlogin);
if (isuserlogin) {
dispatch(setUser(parsedUser));
navigation.reset({
index: 0,
routes: [{ name: 'Welcome' }],
});
} else {
navigation.reset({
index: 0,
routes: [{ name: 'Login' }],
});
}
} catch (error) {
console.error('Error checking login status:', error);
}
};
checkLoginStatus();
}, []);
return (
<View style={styles.container}>
<View style={styles.backgroundContainer}>
<Image
source={IMAGES.splashFullImg}
resizeMode="stretch"
style={styles.backdrop}
/>
</View>
<View style={styles.overlay}>
<Image style={styles.logo} source={IMAGES.AppLogo} />
</View>
</View>
);
};
export default SplashScreen;
const styles = StyleSheet.create({
backgroundContainer: {
position: 'absolute',
top: 0,
bottom: 0,
left: 0,
right: 0,
},
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
},
overlay: {
opacity: 1,
alignItems: 'center',
justifyContent: 'center',
},
logo: {
backgroundColor: 'rgba(0,0,0,0)',
height: 200,
width: 200,
overflow: 'hidden',
resizeMode: 'contain',
marginTop: 5,
justifyContent: 'center',
},
backdrop: {
flex: 1,
width: '100%',
height: '100%',
},
headline: {
fontSize: 18,
textAlign: 'center',
backgroundColor: 'black',
color: 'white',
//borderWidth:1
},
});
+219
View File
@@ -0,0 +1,219 @@
import React, { useEffect, useState } from 'react';
import { View, Text, Image, TouchableOpacity, ImageBackground } from 'react-native';
import { KeyboardAwareScrollView } from 'react-native-keyboard-aware-scroll-view';
import { useDispatch } from 'react-redux';
import { OtpInput } from "react-native-otp-entry";
import CustomButton from '../../../components/CustomButton';
import Background from '../../../components/Background';
import IMAGES from '../../../constants/Images';
import { GlobalTheme } from '../../../theme';
import { styles } from './style';
import { toastError, toastSuccess } from '../../../constants/Toast';
import Loader from '../../../constants/Loader';
import { ApiURL } from '../../../api/ApiConstant';
import AsyncStorage from '@react-native-async-storage/async-storage';
import { setUser } from '../../../redux/slices/userSlice';
import axios from 'axios';
import { SafeAreaView } from 'react-native-safe-area-context';
const VerifyOTP = ({ navigation, route }) => {
const [loading, setLoading] = useState(false);
const dispatch = useDispatch();
const [otp, setOTP] = useState('');
const [timer, setTimer] = useState(300); // 5 minutes = 300 seconds
const [showResend, setShowResend] = useState(false);
const username = route.params.username;
useEffect(() => {
if (timer === 0) {
setShowResend(true);
return;
}
const interval = setInterval(() => {
setTimer(prev => prev - 1);
}, 1000);
return () => clearInterval(interval);
}, [timer]);
const formatTime = (secs) => {
const minutes = Math.floor(secs / 60);
const seconds = secs % 60;
return `${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}`;
};
const handle_validate = () => {
if (otp.length < 6) {
toastError('Alert', "Please enter 6 digit PIN");
} else {
onSubmit();
}
}
const resendOTP = async () => {
try {
const params = {
"UserId": username
}
const config = {
method: 'post',
url: ApiURL.getotpApi,
headers: {
'api_key': '9a1f056fecb84eaf8eb4152dda22ab0501955c4f9bbe7daa8780740459fdde7a',
'Content-Type': 'application/json'
},
data: params
};
const response = await axios.request(config);
const res = response.data || [];
// console.log('storeSearchApi====>', res);
if (res?.SendOTP[0].OTP === '0' || res?.SendOTP[0].OTP === 0) {
toastError("Alert", res?.SendOTP[0]?.Messages)
} else {
toastSuccess("Alert", res?.SendOTP[0]?.Messages)
navigation.navigate('VerifyOTP', { username: username });
}
// console.log('getotpApi res==>', JSON.stringify(res?.SendOTP[0]));
// setLoading(false)
} catch (error) {
// setLoading(false)
console.log("❌ Filter API error:", error);
}
};
const resend_OTP = () => {
setTimer(300);
setShowResend(false);
toastSuccess('Resend OTP Successfully.')
resendOTP();
}
const onSubmit = () => {
setLoading(true)
VerifyOTP();
setTimeout(() => {
setLoading(false)
}, 100);
}
const VerifyOTP = async () => {
try {
const params = {
"UserId": username,
"OTP": otp
}
const config = {
method: 'post',
url: ApiURL.verifyotpApi,
headers: {
'api_key': '9a1f056fecb84eaf8eb4152dda22ab0501955c4f9bbe7daa8780740459fdde7a',
'Content-Type': 'application/json'
},
data: params
};
const response = await axios.request(config);
const res = response.data || [];
if (res?.AuthenticateOTP[0].Message == 'OTP is matched') {
toastSuccess("Alert", res?.AuthenticateOTP[0]?.Message || "Alert", "Login Successfully.")
await AsyncStorage.setItem('@Dabur_DNA_User', JSON.stringify(res?.AuthenticateOTP[0]));
dispatch(setUser(res?.AuthenticateOTP[0]));
navigation.reset({ index: 0, routes: [{ name: 'Welcome' }] })
} else {
toastError("Alert", res?.AuthenticateOTP[0]?.Message);
}
} catch (error) {
// setLoading(false)
console.log("❌ Filter API error:", error);
}
}
return (
<SafeAreaView style={styles.container}>
<TouchableOpacity onPress={() => navigation.goBack()}>
<View style={styles.backTextView}>
<Image source={IMAGES.leftArrowIcon} style={styles.iconStyle} />
<Text style={styles.backIconText}>Back</Text>
</View>
</TouchableOpacity>
<KeyboardAwareScrollView keyboardShouldPersistTaps="handled" enableOnAndroid={true} style={styles.container} contentContainerStyle={{ justifyContent: 'center', flex: 1 }}>
<View style={{ backgroundColor: GlobalTheme.colors.primary, height: 40 }} />
<Image style={styles.appLogo} source={IMAGES.AuthTopBGNew} resizeMode='contain' />
<View style={styles.card}>
<View style={styles.titleContainer}>
<Text style={styles.titleText}>Enter the OTP sent to your {"\n"} registered contact</Text>
</View>
<View style={{ marginTop: 50 }}>
<OtpInput
numberOfDigits={6}
focusColor={GlobalTheme.colors.primary}
autoFocus={false}
hideStick={true}
placeholder=""
blurOnFilled={true}
disabled={false}
type="numeric"
secureTextEntry={false}
focusStickBlinkingDuration={500}
// onFocus={() => console.log("Focused")}
// onBlur={() => console.log("Blurred")}
onTextChange={(text) => setOTP(text)}
onFilled={(text) => {
setOTP(text);
console.log(`OTP is ${text}`);
}}
textInputProps={{
accessibilityLabel: "One-Time Password",
}}
textProps={{
accessibilityRole: "text",
accessibilityLabel: "OTP digit",
allowFontScaling: false,
}}
theme={{
containerStyle: styles.container,
pinCodeContainerStyle: styles.pinCodeContainer,
pinCodeTextStyle: styles.pinCodeText,
focusStickStyle: styles.focusStick,
focusedPinCodeContainerStyle: styles.activePinCodeContainer,
placeholderTextStyle: styles.placeholderText,
filledPinCodeContainerStyle: styles.filledPinCodeContainer,
disabledPinCodeContainerStyle: styles.disabledPinCodeContainer,
}}
/>
</View>
<View style={{ marginTop: 100}}>
<CustomButton onPress={handle_validate} title={'Verify'} style={styles.btnbg} textstyle={styles.btntext} />
</View>
<View style={{ alignItems: 'center', marginTop: 20 }}>
{!showResend ? (
<Text style={{ color: GlobalTheme.colors.darkGray }}>Resend OTP in {formatTime(timer)}</Text>
) : (
<TouchableOpacity onPress={resend_OTP}>
<Text style={styles.resendOTP} >
Resend OTP
</Text>
</TouchableOpacity>
)}
</View>
<ImageBackground source={IMAGES.AuthBottomBG} style={styles.AuthBottomBG} >
{/* <Image source={IMAGES.Logo} style={styles.footerImage} resizeMode="contain"/> */}
<Text style={{ position : 'absolute', textAlign:'center', alignContent:'center',alignSelf:'center', bottom:0 , color : GlobalTheme.colors.gray}}> Copyright CPM India - 2025</Text>
</ImageBackground>
</View>
</KeyboardAwareScrollView>
<Loader visible={loading} />
</SafeAreaView>
);
};
export default VerifyOTP;
+149
View File
@@ -0,0 +1,149 @@
import { Dimensions, StyleSheet } from 'react-native';
import { GlobalTheme, Screen } from '../../../theme';
import { normalize } from '../../../utilis/responsive';
const { width, height } = Dimensions.get('window');
export const styles = StyleSheet.create({
container: {
// flex: 1,
// paddingHorizontal: 5
flex: 1,
backgroundColor: GlobalTheme.colors.primary,
},
logoContainer: {
alignItems: 'center',
},
appLogo: {
height: normalize(170),
width: '100%',
resizeMode: 'contain',
marginTop: 40
},
titleContainer: {
alignSelf: 'center',
},
titleText: {
fontSize: 18,
fontWeight: '500',
color: GlobalTheme.colors.black,
textAlign: 'center'
},
btnbg: {
backgroundColor: GlobalTheme.colors.secondary, borderRadius: GlobalTheme.borderRadius.md
},
btntext: {
color: GlobalTheme.colors.white,
fontSize: GlobalTheme.typography.fontSize.medium
},
otp_inputStyle: {
textAlign: 'center',
backgroundColor: 'red',
width: 100,
borderRadius: 4,
paddingVertical: 10,
paddingHorizontal: 15,
height: 50,
color: 'red',
marginBottom: 10,
marginRight: 10,
fontSize: 25,
},
// OTP
pinCodeContainer: {
width: 45,
height: 55,
borderWidth: 1,
borderColor: '#D8E3F1',
borderRadius: 8,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#fff',
},
activePinCodeContainer: {
borderColor: GlobalTheme.colors.primary,
borderWidth: 2,
},
filledPinCodeContainer: {
backgroundColor: '#D8E3F1',
},
disabledPinCodeContainer: {
backgroundColor: '#f0f0f0',
borderColor: '#ddd',
},
pinCodeText: {
fontSize: 20,
fontWeight: 'bold',
color: '#333',
},
focusStick: {
height: 2,
width: 20,
backgroundColor: GlobalTheme.colors.primary,
marginTop: 4,
},
placeholderText: {
color: '#aaa',
fontSize: 18,
},
resendText: {
color: GlobalTheme.colors.primary,
fontWeight: 'bold',
fontSize: 14,
marginTop: 20,
textAlign: 'center',
},
timerText: {
color: 'gray',
fontSize: 14,
marginTop: 20,
textAlign: 'center',
},
resendOTP: {
color: GlobalTheme.colors.secondary, fontWeight: GlobalTheme.typography.fontWeight.medium, fontSize: GlobalTheme.typography.fontSize.small
},
iconStyle: {
height: 20,
width: 20,
resizeMode: 'contain',
tintColor: GlobalTheme.colors.white
},
backIconText: { fontSize: GlobalTheme.typography.fontSize.small, color: GlobalTheme.colors.white, fontWeight: GlobalTheme.typography.fontWeight.medium, marginLeft: 8 },
backTextView: {
flexDirection: 'row', alignItems: 'center', paddingTop: 20, paddingHorizontal: 10
},
card: {
backgroundColor: GlobalTheme.colors.white,
borderTopLeftRadius: GlobalTheme.borderRadius.xxlg || normalize(20),
borderTopRightRadius: GlobalTheme.borderRadius.xxlg || normalize(20),
padding: normalize(20),
width: width,
alignSelf: 'center',
// marginTop: normalize(30),
shadowColor: '#000',
shadowOpacity: 0.05,
shadowOffset: { width: 0, height: 4 },
shadowRadius: 8,
elevation: 4,
flex: 1,
minHeight : height * 0.7
},
footer: {
marginTop: 60,
alignItems: 'center',
},
footerImage: {
width: '100%',
height: normalize(80),
resizeMode: 'contain',
marginTop: normalize(160),
},
AuthBottomBG: {
width: '100%',
resizeMode: 'contain',
height: normalize(270),
justifyContent: 'center',
marginTop: normalize(-40),
},
});
@@ -0,0 +1,209 @@
{
"subTabs": [
{
"TabId": 1,
"TabName": "PSS Score",
"TabRow": 1,
"TabCol": 1
},
{
"TabId": 2,
"TabName": "SOS Actual",
"TabRow": 1,
"TabCol": 2
},
{
"TabId": 3,
"TabName": "SOS Compliance",
"TabRow": 1,
"TabCol": 3
},
{
"TabId": 4,
"TabName": "OSA",
"TabRow": 1,
"TabCol": 4
},
{
"TabId": 5,
"TabName": "Asset",
"TabRow": 1,
"TabCol": 5
},
{
"TabId": 6,
"TabName": "Promotion",
"TabRow": 1,
"TabCol": 6
}
],
"graphDetails": [
{
"TabId": 1,
"GraphId": 1,
"GraphType": "ScoreCard",
"GraphTitle": "PSS Score",
"GraphUrl": "http://localhost:3000/MTD/PSSScore",
"GraphBackground": "#F4EAFF",
"GraphOptions": {}
},
{
"TabId": 1,
"GraphId": 2,
"GraphType": "ScoreCard",
"GraphTitle": "SOS Actual",
"GraphUrl": "http://localhost:3000/MTD/SOSActual",
"GraphBackground": "#FFF3ED",
"GraphOptions": {}
},
{
"TabId": 1,
"GraphId": 3,
"GraphType": "ScoreCard",
"GraphTitle": "SOS Compliance",
"GraphUrl": "http://localhost:3000/MTD/SOSCompliance",
"GraphBackground": "#FFFEF0",
"GraphOptions": {}
},
{
"TabId": 1,
"GraphId": 4,
"GraphType": "ScoreCard",
"GraphTitle": "OSA",
"GraphUrl": "http://localhost:3000/MTD/OSA",
"GraphBackground": "#ECFFFA",
"GraphOptions": {}
},
{
"TabId": 1,
"GraphId": 5,
"GraphType": "ScoreCard",
"GraphTitle": "Asset",
"GraphUrl": "http://localhost:3000/MTD/Asset",
"GraphBackground": "#EBECFF",
"GraphOptions": {}
},
{
"TabId": 1,
"GraphId": 6,
"GraphType": "ScoreCard",
"GraphTitle": "Promotion",
"GraphUrl": "http://localhost:3000/MTD/Promotion",
"GraphBackground": "#FFFEF0",
"GraphOptions": {}
},
{
"TabId": 2,
"GraphId": 1,
"GraphType": "ScoreCard",
"GraphTitle": "SOS Actual",
"GraphUrl": "http://localhost:3000/MTD/SOSActual",
"GraphBackground": "#ECFFFA",
"GraphOptions": {}
},
{
"TabId": 2,
"GraphId": 2,
"GraphType": "BarGraph",
"GraphTitle": "SOS Actual Trend",
"GraphUrl": "http://localhost:3000/MTD/SOSActualTrend",
"GraphBackground": "#F4EAFF",
"GraphOptions": {
"axisX": "month",
"axisY": "score",
"labelShow": 1,
"barColors": [
"#ffaa11",
"#ffbb11",
"#ffcc11"
],
"gridLinesH": 1,
"gridLinesV": 1
}
},
{
"TabId": 3,
"GraphId": 3,
"GraphType": "ScoreCard",
"GraphTitle": "SOS Compliance",
"GraphUrl": "http://localhost:3000/MTD/SOSActual",
"GraphBackground": "#ECFFFA",
"GraphOptions": {}
},
{
"TabId": 3,
"GraphId": 4,
"GraphType": "BarGraph",
"GraphTitle": "SOS Compliance Trend",
"GraphUrl": "http://localhost:3000/MTD/SOSActualTrend",
"GraphBackground": "#fff",
"GraphOptions": {
"axisX": "month",
"axisY": "score",
"labelShow": 1,
"barColors": [
"#ffaa11",
"#ffbb11",
"#ffcc11"
],
"gridLinesH": 1,
"gridLinesV": 1
}
},
{
"TabId": 5,
"GraphId": 5,
"GraphType": "ScoreCard",
"GraphTitle": "Asset",
"GraphUrl": "http://localhost:3000/MTD/SOSActual",
"GraphBackground": "#ECFFFA",
"GraphOptions": {}
},
{
"TabId": 5,
"GraphId": 6,
"GraphType": "Table",
"GraphTitle": "Asset Details",
"GraphUrl": "http://localhost:3000/MTD/SOSActual",
"GraphBackground": "#ECFFFA",
"GraphOptions": {}
},
{
"TabId": 4,
"GraphId": 7,
"GraphType": "ScoreCard",
"GraphTitle": "OSA",
"GraphUrl": "http://localhost:3000/MTD/SOSActual",
"GraphBackground": "#ECFFFA",
"GraphOptions": {}
},
{
"TabId": 4,
"GraphId": 8,
"GraphType": "PieChart",
"GraphTitle": "OSA",
"GraphUrl": "http://localhost:3000/MTD/SOSActual",
"GraphBackground": "#ECFFFA",
"GraphOptions": {}
},
{
"TabId": 6,
"GraphId": 9,
"GraphType": "ScoreCard",
"GraphTitle": "Promotion",
"GraphUrl": "http://localhost:3000/MTD/SOSActual",
"GraphBackground": "#ECFFFA",
"GraphOptions": {}
},
{
"TabId": 6,
"GraphId": 10,
"GraphType": "LineChart",
"GraphTitle": "Promotion",
"GraphUrl": "http://localhost:3000/MTD/SOSActual",
"GraphBackground": "#ECFFFA",
"GraphOptions": {}
}
]
}
@@ -0,0 +1,906 @@
import { View, Text, StyleSheet, Image, TouchableOpacity, Dimensions, TextInput, ScrollView, FlatList, Button, Alert, Modal, TouchableWithoutFeedback, StatusBar } from 'react-native'
import React, { useEffect, useRef, useState } from 'react'
import RBSheet from 'react-native-raw-bottom-sheet';
import { styles } from './style';
import LinearGradient from 'react-native-linear-gradient';
import CustomHeader from '../../../components/CustomHeader';
import IMAGES from '../../../constants/Images';
import CustomButton from '../../../components/CustomButton';
import CustomDropdown from '../../../components/CustomDropdown';
import { GlobalTheme } from '../../../theme';
import { SafeAreaView } from 'react-native-safe-area-context';
import displayData from './display.json'
import { BarChart, LineChart, PieChart } from 'react-native-chart-kit';
import { getDownloadJson } from '../../../constants/function';
import db, { getAllFromTable } from '../../../constants/database';
import { post } from '../../../api/ApiService';
import { ApiURL } from '../../../api/ApiConstant';
import mainDisplayJson from './mainDisplay.json'
const Dashboard = (props) => {
const { navigation } = props;
const screenHeight = Dimensions.get('screen').height;
const screenWidth = Dimensions.get('screen').width;
const refRBSheet = useRef();
const [isStoreSelected, setIsStoreSelected] = useState(false)
const [searchResult, setSearchResult] = useState(false)
const [storeData, setStoreData] = useState({})
const [mainTab, setMainTab] = useState('mtd')
const [activeTab, setActiveTab] = useState(displayData?.subTabs?.[0]?.TabId || 1)
const [state, setState] = useState(null);
const [year, setYear] = useState(null)
const [month, setMonth] = useState(null)
const [city, setCity] = useState(null);
const [asmArea, setAsmArea] = useState(null);
const scrollViewRef = useRef(null);
const [showButton, setShowButton] = useState(false);
const [modalVisible, setModalVisible] = useState(false);
// Handle scroll event to show/hide button
const handleScroll = (event) => {
const offsetY = event.nativeEvent.contentOffset.y;
setShowButton(offsetY > 0); // Show button only if not at the top
};
const openBottomSheet = () => {
refRBSheet.current.open()
}
const onSelectStore = (item) => {
setStoreData(item)
setIsStoreSelected(true)
refRBSheet.current.close()
}
const data = [
{ label: 'Delhi', value: '1' },
{ label: 'Mumbai', value: '2' },
];
const cityData = [
{ label: 'Okhla', value: '1' },
{ label: 'Kalkaji', value: '2' },
{ label: 'South Ex.', value: '3' },
];
const storeJson = [
{
id: 1,
name: "Banarasi & Sons",
address: "G-8, Mahakavi Goswami Tulsidas Marg, Paraag Vihar, Press Colony, Hari Nagar, New Delhi, 110027",
status: "pending"
},
{
id: 2,
name: "Gupta store & Sons",
address: "G-8, Mahakavi Goswami Tulsidas Marg, Paraag Vihar, Press Colony, Hari Nagar, New Delhi, 110027",
status: "completed"
},
{
id: 3,
name: "Bansal General store",
address: "G-8, Mahakavi Goswami Tulsidas Marg, Paraag Vihar, Press Colony, Hari Nagar, New Delhi, 110027",
status: "completed"
},
{
id: 4,
name: "Mohan Mahalaxmi store",
address: "G-8, Mahakavi Goswami Tulsidas Marg, Paraag Vihar, Press Colony, Hari Nagar, New Delhi, 110027",
status: "completed"
},
{
id: 5,
name: "Chawla Store",
address: "G-8, Mahakavi Goswami Tulsidas Marg, Paraag Vihar, Press Colony, Hari Nagar, New Delhi, 110027",
status: "completed"
},
{
id: 6,
name: "Chawla Store",
address: "G-8, Mahakavi Goswami Tulsidas Marg, Paraag Vihar, Press Colony, Hari Nagar, New Delhi, 110027",
status: "completed"
},
{
id: 7,
name: "Chawla Store",
address: "G-8, Mahakavi Goswami Tulsidas Marg, Paraag Vihar, Press Colony, Hari Nagar, New Delhi, 110027",
status: "completed"
},
{
id: 8,
name: "Chawla Store",
address: "G-8, Mahakavi Goswami Tulsidas Marg, Paraag Vihar, Press Colony, Hari Nagar, New Delhi, 110027",
status: "completed"
},
{
id: 9,
name: "Chawla Store",
address: "G-8, Mahakavi Goswami Tulsidas Marg, Paraag Vihar, Press Colony, Hari Nagar, New Delhi, 110027",
status: "completed"
},
];
const visitedStoreData = [
{
id: 1,
name: "Chawla Store",
address: "G-8, Mahakavi Goswami Tulsidas Marg, Paraag Vihar, Press Colony, Hari Nagar, New Delhi, 110027",
status: "pending"
},
{
id: 2,
name: "Mohan Mahalaxmi store",
address: "G-8, Mahakavi Goswami Tulsidas Marg, Paraag Vihar, Press Colony, Hari Nagar, New Delhi, 110027",
status: "completed"
},
{
id: 3,
name: "Bansal General store",
address: "G-8, Mahakavi Goswami Tulsidas Marg, Paraag Vihar, Press Colony, Hari Nagar, New Delhi, 110027",
status: "completed"
},
{
id: 4,
name: "Bansal General store",
address: "G-8, Mahakavi Goswami Tulsidas Marg, Paraag Vihar, Press Colony, Hari Nagar, New Delhi, 110027",
status: "completed"
},
{
id: 5,
name: "Bansal General store",
address: "G-8, Mahakavi Goswami Tulsidas Marg, Paraag Vihar, Press Colony, Hari Nagar, New Delhi, 110027",
status: "completed"
},
{
id: 6,
name: "Bansal General store",
address: "G-8, Mahakavi Goswami Tulsidas Marg, Paraag Vihar, Press Colony, Hari Nagar, New Delhi, 110027",
status: "completed"
},
];
const monthData = [
{ label: 'January', value: '1' },
{ label: 'February', value: '2' },
{ label: 'March', value: '3' },
{ label: 'April', value: '4' },
{ label: 'May', value: '5' },
{ label: 'June', value: '6' },
{ label: 'July', value: '7' },
{ label: 'August', value: '8' },
{ label: 'September', value: '9' },
{ label: 'October', value: '10' },
{ label: 'November', value: '11' },
{ label: 'December', value: '12' },
];
const yearData = [
{ label: '2025', value: '2025' },
{ label: '2024', value: '2024' },
{ label: '2023', value: '2023' },
{ label: '2022', value: '2022' },
{ label: '2021', value: '2021' },
{ label: '2020', value: '2020' },
{ label: '2019', value: '2019' },
{ label: '2018', value: '2018' },
{ label: '2017', value: '2017' },
{ label: '2016', value: '2016' },
{ label: '2015', value: '2015' },
];
const assetData = [
{
"section": "Asset%",
"data": [
{ "display": "Real Endcap", "present": "No" },
{ "display": "Honey Parasite", "present": "Yes" },
{ "display": "Odonil Floor Stack", "present": "YHes" }
]
},
{
"section": "Additional Visibility",
"data": [
{ "display": "Active 1 Ltr Endcap", "present": "Yes" },
{ "display": "Chyawanprash Stack", "present": "Yes" }
]
},
{
"section": "Promotion",
"data": [
{ "display": "Activ 100% Juice", "present": "Yes" },
{ "display": "Airfresher", "present": "No" },
{ "display": "BABY CARE", "present": "No" },
{ "display": "Chyawanprash", "present": "No" },
{ "display": "Hair Oil", "present": "No" },
]
}
]
const PieData = [
{
name: "Seoul",
population: 21500000,
color: "rgba(131, 167, 234, 1)",
legendFontColor: "#7F7F7F",
legendFontSize: 15
},
{
name: "Toronto",
population: 2800000,
color: "#F00",
legendFontColor: "#7F7F7F",
legendFontSize: 15
},
{
name: "Beijing",
population: 527612,
color: "red",
legendFontColor: "#7F7F7F",
legendFontSize: 15
},
{
name: "New York",
population: 8538000,
color: "#ffffff",
legendFontColor: "#7F7F7F",
legendFontSize: 15
},
{
name: "Moscow",
population: 11920000,
color: "rgb(0, 0, 255)",
legendFontColor: "#7F7F7F",
legendFontSize: 15
}
];
const barData = {
labels: ["Jan", "Feb", "March", "April",],
datasets: [
{
data: [20, 45, 28, 80]
}
]
};
const chartConfig = {
backgroundColor: '#ffffff',
backgroundGradientFrom: '#ffffff',
backgroundGradientTo: '#ffffff',
fillShadowGradientFrom: '#FF8C61', // For bar colors
fillShadowGradientTo: '#FF8C61',
fillShadowGradientFromOpacity: 1,
fillShadowGradientToOpacity: 1,
decimalPlaces: 0,
color: (opacity = 1) => `rgba(0, 0, 0, ${opacity})`, // Text color
labelColor: (opacity = 1) => `rgba(0, 0, 0, ${opacity})`,
// barPercentage: 0.5,
barPercentage: 1,
};
const onSelectSubTab = (item) => {
setActiveTab(item?.TabId)
}
const filteredGraphs = displayData?.graphDetails?.filter(graph => graph.TabId === activeTab);
const firstItem = filteredGraphs?.[0];
const restItems = filteredGraphs?.slice(1);
const renderItem = ({ item }) => {
switch (item.GraphType) {
case "ScoreCard":
return (
<View style={[styles.percentBox, { backgroundColor: item.GraphBackground }]}>
<Text style={styles.boxText}>{item.GraphTitle}</Text>
<Text style={[styles.boxText, { fontWeight: '500', fontSize: 24, marginTop: 10 }]}>{"45%"}</Text>
</View>
);
case "BarGraph":
const barData = {
labels: ["Jan", "Feb", "Mar", "Apr"],
datasets: [{ data: [35, 45, 20, 55] }]
};
return (
<View style={{ padding: 10, backgroundColor: '', borderRadius: 8 }}>
<Text style={{ fontSize: 16, fontWeight: '600', marginBottom: 10 }}>
SOS Compliance Trend
</Text>
<BarChart
data={barData}
width={screenWidth - 40}
height={220}
yAxisSuffix="%"
chartConfig={chartConfig}
fromZero
showValuesOnTopOfBars
withInnerLines
/>
</View>
);
case "Table":
return (
<View style={{ flex: 1, }}>
<ScrollView>
<View style={{ flexDirection: 'row', borderWidth: 1, borderColor: '#EAEAEA', alignItems: 'center', alignSelf: 'flex-start', padding: 5, borderRadius: 15 }}>
<TouchableOpacity
style={[styles.subTab, { backgroundColor: '#113F8C' }]}>
<Text style={[styles.tabText, { color: "#fff" }]}>{'Asset'}</Text>
</TouchableOpacity>
<TouchableOpacity
style={[styles.subTab, { backgroundColor: '#113F8C' }]}>
<Text style={[styles.tabText, { color: "#fff" }]}>{'Asset Details'}</Text>
</TouchableOpacity>
</View>
{assetData && assetData.map((table, index) => (
<View key={index} style={{ marginTop: 10 }}>
<Text style={{ fontSize: 16, color: '#000', fontWeight: '500' }}>{table.section}</Text>
<View style={{ marginTop: 5, backgroundColor: '#fff', elevation: 5, padding: 10, borderRadius: 10 }}>
<View style={{ flexDirection: 'row', borderBottomWidth: 1, borderColor: '#E0E0E0', paddingBottom: 7, }}>
<Text style={{ width: '50%', color: '#676767', fontSize: 14 }}>Display</Text>
<Text style={{ width: '50%', color: '#676767', fontSize: 14 }}>Present</Text>
</View>
{
table && table.data.map((item) => (
<View style={{ marginTop: 3, flexDirection: 'row', paddingBottom: 7, borderBottomWidth: 1, borderColor: '#E0E0E0', }}>
<Text style={{ width: '50%', color: '#000', fontSize: 14, }}>{item.display}</Text>
<Text style={{ width: '50%', color: '#000', fontSize: 14, }}>{item?.present}</Text>
</View>
))}
</View>
</View>
))}
<View style={{ marginBottom: 500 }} />
</ScrollView>
</View>
)
case "LineChart":
return (
<View style={{ flex: 1, }}>
<Text>Bezier Line Chart</Text>
<LineChart
data={{
labels: ["January", "February", "March", "April", "May", "June"],
datasets: [
{
data: [
Math.random() * 100,
Math.random() * 100,
Math.random() * 100,
Math.random() * 100,
Math.random() * 100,
Math.random() * 100
]
}
]
}}
width={Dimensions.get("window").width} // from react-native
height={220}
yAxisLabel="$"
yAxisSuffix="k"
yAxisInterval={1} // optional, defaults to 1
chartConfig={{
backgroundColor: "#e26a00",
backgroundGradientFrom: "lgray",
backgroundGradientTo: "lightblue",
decimalPlaces: 2, // optional, defaults to 2dp
color: (opacity = 1) => `rgba(255, 255, 255, ${opacity})`,
labelColor: (opacity = 1) => `rgba(255, 255, 255, ${opacity})`,
style: {
borderRadius: 16
},
propsForDots: {
r: "6",
strokeWidth: "2",
stroke: "#ffa726"
}
}}
bezier
style={{
marginVertical: 8,
borderRadius: 16
}}
/>
</View>
)
case "PieChart":
return (
<View style={{ flex: 1, }}>
<PieChart
data={PieData}
width={screenWidth}
height={220}
chartConfig={chartConfig}
accessor={"population"}
backgroundColor={"transparent"}
paddingLeft={"15"}
center={[10, 50]}
absolute
/>
</View>
)
default:
return (
<View style={[styles.percentBox, { backgroundColor: "#f8d7da", padding: 10, marginVertical: 5 }]}>
<Text style={[styles.boxText, { color: "#721c24" }]}>
Unsupported Graph Type: {item.GraphType}
</Text>
</View>
);
}
};
// download data
useEffect(() => {
const init = async () => {
await getData(); // <-- Only dynamic live data
};
init();
getTabData()
}, []);
// Insert fallback mock data if empty
const insertMasterSurveyQuestion = (data_arr = []) => {
return new Promise((resolve, reject) => {
if (!Array.isArray(data_arr) || data_arr.length === 0) {
resolve(true);
return;
}
const escape = str => (str || '').toString().replace(/'/g, "''");
const values = data_arr.map(item => {
const {
MenuId, SurveyId, SurveyName, CategoryId, Category, CategorySequence,
QuestionId, Question, QuestionType, QuestionImageAllow, QEnable,
LengthValidation, MinLength, MaxLength, OTP, DateRange, QuestionSequence,
AnswerId, Answer, ImageAllow1, ImageAllow2, Image1Mandatory, Image2Mandatory,
QuestionImageMandatory, EnableQuestion, DisableQuestion, AnswerSequence,
ShowCat, SubCategoryId, SubCategory, SubCategorySequence,
QuestionTypeNew, CalFormula, RefImage, QuestionRefImage
} = item;
return `(
'${MenuId}', '${SurveyId}', '${escape(SurveyName)}', '${CategoryId}', '${escape(Category)}',
'${CategorySequence}', '${QuestionId}', '${escape(Question)}', '${escape(QuestionType)}',
'${QuestionImageAllow ? 1 : 0}', '${QEnable ? 1 : 0}', '${LengthValidation ? 1 : 0}',
'${MinLength}', '${MaxLength}', '${OTP ? 1 : 0}', '${escape(DateRange)}',
'${QuestionSequence}', '${AnswerId}', '${escape(Answer)}',
'${ImageAllow1 ? 1 : 0}', '${ImageAllow2 ? 1 : 0}', '${Image1Mandatory ? 1 : 0}',
'${Image2Mandatory ? 1 : 0}', '${QuestionImageMandatory ? 1 : 0}',
'${escape(EnableQuestion)}', '${escape(DisableQuestion)}', '${AnswerSequence}',
'${ShowCat ? 1 : 0}', '${SubCategoryId}', '${escape(SubCategory)}',
'${SubCategorySequence}', '${escape(QuestionTypeNew)}', '${escape(CalFormula)}',
'${escape(RefImage)}', '${escape(QuestionRefImage)}'
)`;
});
const sql = `
INSERT INTO FeedbackCategory (
MenuId, SurveyId, SurveyName, CategoryId, Category, CategorySequence,
QuestionId, Question, QuestionType, QuestionImageAllow, QEnable,
LengthValidation, MinLength, MaxLength, OTP, DateRange, QuestionSequence,
AnswerId, Answer, ImageAllow1, ImageAllow2, Image1Mandatory, Image2Mandatory,
QuestionImageMandatory, EnableQuestion, DisableQuestion, AnswerSequence,
ShowCat, SubCategoryId, SubCategory, SubCategorySequence,
QuestionTypeNew, CalFormula, RefImage,QuestionRefImage
) VALUES ${values.join(',')}
`;
db.transaction(tx => {
tx.executeSql(`DELETE FROM FeedbackCategory`, [], () => {
tx.executeSql(sql, [], () => {
console.log('FeedbackCategory inserted successfully');
resolve(true);
}, (err) => {
console.log('Insert error in FeedbackCategory:', err);
reject(err);
});
}, (err) => {
console.log('Delete error before insert FeedbackCategory:', err);
reject(err);
});
});
});
};
async function getData() {
try {
const data1 = {
Downloadtype: "Master_SurveyQuestion",
Param1: "",
Param2: "",
username: "testmer",
};
const url = "https://di1.parinaam.in/Webservice/GenericService.svc/DownloadJson";
console.log("Downloading from:", url, data1);
const res = await getDownloadJson(url, data1);
console.log('Response:', res);
if (res?.Master_SurveyQuestion?.length) {
const data = res.Master_SurveyQuestion || [];
console.log("Inserting downloaded data into FeedbackCategory...");
const all = await getAllFromTable('FeedbackCategory');
if (all?.length === 0) {
console.log("Table empty. Inserting fallback mock data...");
await insertMasterSurveyQuestion(data);
} else {
console.log("FeedbackCategory table already has data.");
}
} else {
console.log("No Master_SurveyQuestion data found in response");
}
} catch (err) {
console.log("Dashboard feedback survey download error:", err);
}
}
// end download data.
const getTabData = () => {
let params = {
"parameters": {
"projectid": 41654,
"year": 2025,
"monthno": 6,
"storeid": 2702
}
}
post(ApiURL.pssscoreApi, params)
.then(res => {
console.log('psscoreApi res==>', res);
})
}
return (
<SafeAreaView style={{ flex: 1, backgroundColor: GlobalTheme.colors.primary }}>
<View style={{ flex: 1, backgroundColor: '#fff' }}>
{!isStoreSelected ?
<CustomHeader
title=" Dashboard"
rightIcon={IMAGES.menuIcon}
onRightPress={() => setModalVisible(true)}
/>
:
<CustomHeader
title="Dashboard"
leftIcon={IMAGES.backIcon}
onLeftPress={() => setIsStoreSelected(false)}
rightIcon={IMAGES.menuIcon}
onRightPress={() => setModalVisible(true)}
/>
}
{/* MAIN DASHBOARD */}
{!isStoreSelected ? (
<View style={{ flex: 1 }} >
<TouchableOpacity onPress={() => openBottomSheet()} activeOpacity={0.8} style={styles.selectCard}>
<Image source={IMAGES.reportIcon} style={styles.iconStyle} />
<Text style={styles.storeText}> Select Store:</Text>
<Image source={IMAGES.downIcon} style={{ height: 20, width: 20, resizeMode: 'contain' }} />
</TouchableOpacity>
<Text style={styles.selectStoreText}>Select a store to view it's data.</Text>
<LinearGradient
colors={['#D8E7FF', '#FFFFFF',]}
start={{ x: 0, y: 0 }}
end={{ x: 1, y: 1 }}
style={styles.storeGredient}>
<View style={styles.todayStoreCard}>
<View style={{ marginLeft: 5, flexDirection: 'row', padding: 10, alignItems: 'center' }}>
<View style={{ backgroundColor: '#E3EBF8', padding: 5, borderRadius: 7 }}>
<Image source={IMAGES.storeIcon} style={{ height: 18, width: 18 }} />
</View>
<Text style={styles.todayStoreText}>Today's store</Text>
</View>
<View style={{ borderBottomWidth: 1.5, borderColor: '#E3EBF8' }} />
<View style={[styles.row, { paddingHorizontal: 10 }]}>
<View style={{ width: '48%', alignItems: 'center' }}>
<Text style={{ color: '#000', fontSize: 20, fontWeight: 'bold' }}>1</Text>
<Text style={{ color: '#808CA3', fontSize: 15, fontWeight: '400' }}>View</Text>
</View>
<View style={{ borderWidth: 1, height: 50, borderColor: '#E3EBF8' }} />
<View style={{ width: '48%', alignItems: 'center' }}>
<Text style={{ color: '#000', fontSize: 20, fontWeight: 'bold', textAlign: 'right' }}>2</Text>
<Text style={{ color: '#808CA3', fontSize: 15, fontWeight: '400', textAlign: 'right' }}>Feedback</Text>
</View>
</View>
<ScrollView showsVerticalScrollIndicator={false}>
<View style={{ marginTop: 5, marginHorizontal: 10 }}>
{visitedStoreData && visitedStoreData.map(store => (
<TouchableOpacity onPress={() => navigation.navigate("FeedbackCategories")}
key={store.id} style={styles.storeCard}>
<View style={[styles.row, { margin: 0 }]}>
<Text style={styles.cardTextBold}>{store.name}</Text>
{store?.status === 'completed' ?
<Image source={IMAGES.greenTick} style={{ height: 20, width: 20 }} />
:
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
<View style={{ backgroundColor: '#FFF8CD', padding: 5, paddingHorizontal: 7, borderRadius: 15 }}>
<Text style={{ color: 'orange', fontSize: 13, fontWeight: '500' }}>Pending</Text>
</View>
<Image source={IMAGES.rightArrowIcon} style={{ marginLeft: 10, height: 20, width: 20, resizeMode: 'contain', tintColor: '#97ADD6' }} />
</View>
}
</View>
<Text style={styles.cardText}>{store.address}</Text>
</TouchableOpacity>
))}
</View>
</ScrollView>
</View>
</LinearGradient>
</View>
) : (
// STORE DASHBOARD
<View style={styles.mainContainer}>
<View style={styles.row}>
<View style={styles.selectedStoreText}>
<Text style={styles.storeNameText}> {storeData?.name}</Text>
<TouchableOpacity onPress={() => openBottomSheet()} style={styles.filterIcon}>
<Image source={IMAGES.filterIcon} style={{ height: 18, width: 18 }} />
</TouchableOpacity>
</View>
<TouchableOpacity onPress={() => navigation.navigate('StoreInfo')} style={{ width: '25%', alignItems: 'flex-end' }}>
<Text style={styles.storeInfoText}>Store info</Text>
</TouchableOpacity>
</View>
{/* Year & Month Selector */}
<View style={{ flexDirection: 'row', alignItems: 'center', marginHorizontal: 10, marginTop: 5 }}>
<View style={{ width: 100, }}>
<CustomDropdown
data={yearData}
value={year}
placeholder='Year'
onChange={item => setYear(item.value)}
containerStyle={styles.yearDropDown}
/>
</View>
<View style={{ width: 120, marginHorizontal: 10, }}>
<CustomDropdown
data={monthData}
value={month}
placeholder='Month'
onChange={item => setMonth(item.value)}
containerStyle={[styles.yearDropDown, { width: 120 }]}
/>
</View>
<CustomButton title={'Show'}
style={styles.btnbg}
textstyle={styles.btntext}
/>
</View>
<View style={styles.seperator} />
{/* Main Tab */}
<View style={{ flexDirection: 'row' }}>
<TouchableOpacity onPress={() => setMainTab('mtd')}
style={[styles.mtdTab, { borderBottomWidth: mainTab === 'mtd' ? 3 : 0, backgroundColor: mainTab === 'mtd' ? '#EFF6FF' : '#fff' }]} >
<Text style={[styles.mtdTabText, { color: mainTab === 'mtd' ? '#113F8C' : '#000', }]}>MTD</Text>
</TouchableOpacity>
<TouchableOpacity onPress={() => setMainTab('lastVisit')}
style={[styles.mtdTab, { borderBottomWidth: mainTab === 'lastVisit' ? 3 : 0, backgroundColor: mainTab === 'lastVisit' ? '#EFF6FF' : '#fff' }]} >
<Text style={[styles.mtdTabText, { color: mainTab === 'lastVisit' ? '#113F8C' : '#000' }]}>Last Visit</Text>
</TouchableOpacity>
</View>
{/* <View style={{ flexDirection: 'row' }}>
{mainDisplayJson && mainDisplayJson?.Tabs.map((item, index) => (
<TouchableOpacity onPress={() => setMainTab('mtd')}
style={[styles.mtdTab, { borderBottomWidth: mainTab === 'mtd' ? 3 : 0, backgroundColor: mainTab === 'mtd' ? '#EFF6FF' : '#fff' }]} >
<Text style={[styles.mtdTabText, { color: mainTab === 'mtd' ? '#113F8C' : '#000', }]}>MTD</Text>
</TouchableOpacity>
))}
</View> */}
{/* Sub Tab */}
<View style={styles.subTabView}>
<FlatList
data={displayData?.subTabs}
horizontal
keyExtractor={(item) => item.TabId}
showsHorizontalScrollIndicator={false}
contentContainerStyle={{ paddingHorizontal: 10 }}
renderItem={({ item }) => {
const isSelected = activeTab === item.TabId;
return (
<TouchableOpacity onPress={() => onSelectSubTab(item)}
style={[styles.subTab, { backgroundColor: isSelected ? '#113F8C' : '#fff' }]}>
<Text style={[styles.tabText, { color: isSelected ? "#fff" : "#000" }]}>{item.TabName}</Text>
</TouchableOpacity>
)
}}
/>
</View>
{/* Boxes */}
<View style={{ margin: 10 }}>
{/* First full-width item */}
{firstItem && (
<View style={[styles.percentBox, { backgroundColor: firstItem.GraphBackground, width: '100%', elevation: 0 },]}>
<Text style={styles.boxText}>{firstItem.GraphTitle}</Text>
<Text style={[styles.boxText, { fontWeight: '500', fontSize: 24, marginTop: 10 }]}>{"45%"}</Text>
</View>
)}
<FlatList
data={restItems}
keyExtractor={(item, index) => `${item.GraphId}-${index}`}
renderItem={renderItem}
numColumns={2}
columnWrapperStyle={{ justifyContent: 'space-between', marginTop: 10 }}
contentContainerStyle={{ paddingHorizontal: 2, paddingVertical: 2 }}
showsVerticalScrollIndicator={false}
/>
</View>
</View>
)}
{/* Pop-up Modal */}
<Modal transparent visible={modalVisible} animationType="fade" >
<TouchableOpacity style={styles.modalOverlay} activeOpacity={0.1} onPress={() => setModalVisible(false)}>
<View style={styles.modalContainer}>
<TouchableOpacity onPress={() => { navigation.navigate('FeedbackCategories'), setModalVisible(false) }} style={{ padding: 15 }}>
<Text style={{}}>Feedback and Rating</Text>
</TouchableOpacity>
<View style={{ borderBottomWidth: 1, borderColor: '#D8E3F1' }} />
<TouchableOpacity onPress={() => setModalVisible(false)} style={{ padding: 15 }}>
<Text style={{}}>Dashboard</Text>
</TouchableOpacity>
</View>
</TouchableOpacity>
</Modal>
{/* Bottom Sheet */}
<RBSheet
ref={refRBSheet}
useNativeDriver={true}
customStyles={{
wrapper: {
backgroundColor: 'rgba(0,0,0,0.2)',
},
container: {
height: '86%',
backgroundColor: '#fff',
borderTopLeftRadius: 20,
borderTopRightRadius: 20,
paddingBottom: 10,
},
draggableIcon: {
backgroundColor: 'gray',
},
}}
customModalProps={{
animationType: 'slide',
statusBarTranslucent: true,
}}
customAvoidingViewProps={{
enabled: false,
}}>
<View style={{ flex: 1, }}>
<View style={styles.header}>
<Text style={{ color: '#000', fontWeight: '500', fontSize: 16 }}>Select Store</Text>
<TouchableOpacity onPress={() => refRBSheet.current.close()}>
<Image source={IMAGES.crossIcon} style={styles.iconStyle} />
</TouchableOpacity>
</View>
<View style={{ marginVertical: 0, borderBottomWidth: 1, borderColor: '#ccc' }} />
<ScrollView showsVerticalScrollIndicator={false}
ref={scrollViewRef}
onScroll={handleScroll} >
<View style={{ flex: 1, margin: 15, }}>
<View style={[styles.row, {}]}>
<Text style={styles.dropHeaderText}>State</Text>
<Text style={styles.dropHeaderText}> City</Text>
<Text style={styles.dropHeaderText}> ASM Area</Text>
</View>
{/* Dropdown */}
<View style={{ marginTop: 5, flexDirection: 'row', alignItems: 'center' }}>
<CustomDropdown
data={data}
value={state}
onChange={item => setState(item.value)}
containerStyle={{ flex: 1, }}
/>
<CustomDropdown
data={cityData}
value={city}
onChange={item => setCity(item.value)}
containerStyle={{ flex: 1, marginHorizontal: 10 }}
/>
<CustomDropdown
data={cityData}
value={asmArea}
onChange={item => setAsmArea(item.value)}
containerStyle={{ flex: 1, }}
/>
</View>
<View style={[styles.searchBox, { marginTop: 20 }]}>
{/* <Image source={IMAGES.searchIcon} style={{ height: 15, width: 15 }} /> */}
<TextInput
style={styles.inputStyle}
placeholder='Search store...'
/>
</View>
<View style={{ marginTop: 20 }}>
<CustomButton title={'Apply'} onPress={() => setSearchResult(!searchResult)}
style={{ backgroundColor: GlobalTheme.colors.secondary, borderRadius: GlobalTheme.borderRadius.md }}
textstyle={{ color: GlobalTheme.colors.white, fontSize: GlobalTheme.typography.fontSize.medium }}
/>
</View>
{searchResult ?
<View style={{ marginBottom: 70 }}>
<View style={{ marginTop: 20 }}>
<Text style={{ color: '#000', fontSize: 14 }}>11 Store</Text>
</View>
{storeJson && storeJson.map(store => (
<TouchableOpacity onPress={() => onSelectStore(store)}
key={store.id} style={styles.storeCard}>
<Text style={styles.cardTextBold}>{store.name}</Text>
<Text style={styles.cardText}>{store.address}</Text>
</TouchableOpacity>
))}
</View>
: null
}
</View>
</ScrollView>
{showButton && (
<TouchableOpacity style={styles.floatingBtn} onPress={() => { scrollViewRef.current?.scrollTo({ y: 0, animated: true }) }}>
<Image source={IMAGES.upArrow} style={{ height: 15, width: 15 }} />
</TouchableOpacity>
)}
</View>
</RBSheet>
</View>
</SafeAreaView>
)
}
export default Dashboard
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,567 @@
{
"Tabs": [
{
"MainTabId": 1,
"MainTabName": "MTD",
"MainTabData": {
"subTabs": [
{
"TabId": 1,
"TabName": "PSS Score",
"TabRow": 1,
"TabCol": 1
},
{
"TabId": 2,
"TabName": "SOS Actual",
"TabRow": 1,
"TabCol": 2
},
{
"TabId": 3,
"TabName": "SOS Compliance",
"TabRow": 1,
"TabCol": 3
},
{
"TabId": 4,
"TabName": "OSA",
"TabRow": 1,
"TabCol": 4
},
{
"TabId": 5,
"TabName": "Asset",
"TabRow": 1,
"TabCol": 5
},
{
"TabId": 6,
"TabName": "Promotion",
"TabRow": 1,
"TabCol": 6
}
],
"graphDetails": [
{
"TabId": 1,
"GraphId": 1,
"GraphType": "ScoreCard",
"GraphTitle": "PSS Score",
"GraphUrl": "https://dax.parinaam.in/execute/dabur/mtd/pssscore",
"GraphBackground": "#C3D7FF",
"GraphOptions": {}
},
{
"TabId": 1,
"GraphId": 2,
"GraphType": "ScoreCard",
"GraphTitle": "SOS Actual",
"GraphUrl": "https://dax.parinaam.in/execute/dabur/mtd/SOS_Actual_Perc",
"GraphBackground": "#E2C8FE",
"GraphOptions": {}
},
{
"TabId": 1,
"GraphId": 3,
"GraphType": "ScoreCard",
"GraphTitle": "SOS Compliance",
"GraphUrl": "https://dax.parinaam.in/execute/dabur/mtd/SOS_Compliance_Perc",
"GraphBackground": "#FFD7C3",
"GraphOptions": {}
},
{
"TabId": 1,
"GraphId": 4,
"GraphType": "ScoreCard",
"GraphTitle": "OSA",
"GraphUrl": "https://dax.parinaam.in/execute/dabur/mtd/OSA_Perc",
"GraphBackground": "#FFF9A1",
"GraphOptions": {}
},
{
"TabId": 1,
"GraphId": 5,
"GraphType": "ScoreCard",
"GraphTitle": "Asset",
"GraphUrl": "https://dax.parinaam.in/execute/dabur/mtd/Asset_Perc",
"GraphBackground": "#A2F3DE",
"GraphOptions": {}
},
{
"TabId": 1,
"GraphId": 6,
"GraphType": "ScoreCard",
"GraphTitle": "Promotion",
"GraphUrl": "https://dax.parinaam.in/execute/dabur/mtd/Promotion_Perc",
"GraphBackground": "#BFC2FF",
"GraphOptions": {}
},
{
"TabId": 2,
"GraphId": 1,
"GraphType": "ScoreCard",
"GraphTitle": "SOS Actual",
"GraphUrl": "https://dax.parinaam.in/execute/dabur/mtd/SOS_Actual_Perc",
"GraphBackground": "#ECFFFA",
"GraphOptions": {},
"clickable": 1,
"DetailsPage": [
{
"TabId": 2,
"GraphId": 1,
"GraphType": "Table",
"GraphTitle": "SOS - Category",
"GraphUrl": "https://dax.parinaam.in/execute/dabur/detmtd/sos_actual_perc_on_category",
"GraphBackground": "#ECFFFA",
"GraphOptions": {}
}
]
},
{
"TabId": 2,
"GraphId": 2,
"GraphType": "BarGraph",
"GraphTitle": "SOS Actual Trend",
"GraphUrl": "https://dax.parinaam.in/execute/dabur/kpimtd/sos_actual_trend_perc_mtd",
"GraphBackground": "#F4EAFF",
"GraphOptions": {
"axisX": "month",
"axisY": "score",
"labelShow": 1,
"barColors": [
"#11a4ff",
"#0ea3e3",
"#0b9ddb"
],
"gridLinesH": 0,
"gridLinesV": 0
}
},
{
"TabId": 3,
"GraphId": 3,
"GraphType": "ScoreCard",
"GraphTitle": "SOS Compliance",
"GraphUrl": "https://dax.parinaam.in/execute/dabur/mtd/SOS_Compliance_Perc",
"GraphBackground": "#ECFFFA",
"GraphOptions": {},
"clickable": 1,
"DetailsPage": [
{
"TabId": 3,
"GraphId": 3,
"GraphType": "Table",
"GraphTitle": "SOS Compliance - Category",
"GraphUrl": "https://dax.parinaam.in/execute/dabur/detmtd/sos_sompliance_perc_on_category",
"GraphBackground": "#ECFFFA",
"GraphOptions": {}
}
]
},
{
"TabId": 3,
"GraphId": 4,
"GraphType": "BarGraph",
"GraphTitle": "SOS Compliance Trend",
"GraphUrl": "https://dax.parinaam.in/execute/dabur/kpimtd/sos_compliance_trend_perc_mtd",
"GraphBackground": "#fff",
"GraphOptions": {
"axisX": "month",
"axisY": "score",
"labelShow": 1,
"barColors": [
"#11a4ff",
"#0ea3e3",
"#0b9ddb"
],
"gridLinesH": 1,
"gridLinesV": 1
}
},
{
"TabId": 5,
"GraphId": 5,
"GraphType": "ScoreCard",
"GraphTitle": "Asset",
"GraphUrl": "https://dax.parinaam.in/execute/dabur/mtd/Asset_Perc",
"GraphBackground": "#ECFFFA",
"GraphOptions": {},
"clickable": 1,
"DetailsPage": [
{
"TabId": 5,
"GraphId": 5,
"GraphType": "Table",
"GraphTitle": "Asset Availability",
"GraphUrl": "https://dax.parinaam.in/execute/dabur/kpimtd/asset_availability_mtd",
"GraphBackground": "#ECFFFA",
"GraphOptions": {}
},
{
"TabId": 5,
"GraphId": 5,
"GraphType": "Table",
"GraphTitle": "Asset",
"GraphUrl": "https://dax.parinaam.in/execute/dabur/kpimtd/additional_visibility_mtd",
"GraphBackground": "#ECFFFA",
"GraphOptions": {}
}
]
},
{
"TabId": 5,
"GraphId": 6,
"GraphType": "BarGraph",
"GraphTitle": "Asset Details",
"GraphUrl": "https://dax.parinaam.in/execute/dabur/kpimtd/asset_trend_perc_mtd",
"GraphBackground": "#ECFFFA",
"GraphOptions": {}
},
{
"TabId": 4,
"GraphId": 7,
"GraphType": "ScoreCard",
"GraphTitle": "OSA",
"GraphUrl": "https://dax.parinaam.in/execute/dabur/mtd/OSA_Perc",
"GraphBackground": "#ECFFFA",
"GraphOptions": {},
"clickable": 1,
"DetailsPage": [
{
"TabId": 3,
"GraphId": 3,
"GraphType": "Table",
"GraphTitle": "SOS Compliance - Category",
"GraphUrl": "https://dax.parinaam.in/execute/dabur/detmtd/osa_perc_on_category",
"GraphBackground": "#ECFFFA",
"GraphOptions": {}
}
]
},
{
"TabId": 4,
"GraphId": 8,
"GraphType": "BarGraph",
"GraphTitle": "OSA",
"GraphUrl": "https://dax.parinaam.in/execute/dabur/kpimtd/osa_trend_perc_mtd",
"GraphBackground": "#ECFFFA",
"GraphOptions": {}
},
{
"TabId": 6,
"GraphId": 9,
"GraphType": "ScoreCard",
"GraphTitle": "Promotion",
"GraphUrl": "https://dax.parinaam.in/execute/dabur/mtd/Promotion_Perc",
"GraphBackground": "#ECFFFA",
"GraphOptions": {},
"clickable": 1,
"DetailsPage": [
{
"TabId": 6,
"GraphId": 9,
"GraphType": "Table",
"GraphTitle": "Promotion Availability",
"GraphUrl": "https://dax.parinaam.in/execute/dabur/kpimtd/promotion_availability_mtd",
"GraphBackground": "#ECFFFA",
"GraphOptions": {}
}
]
},
{
"TabId": 6,
"GraphId": 10,
"GraphType": "BarGraph",
"GraphTitle": "Promotion",
"GraphUrl": "https://dax.parinaam.in/execute/dabur/kpimtd/promotion_trend_perc_mtd",
"GraphBackground": "#ECFFFA",
"GraphOptions": {}
}
]
}
},
{
"MainTabId": 2,
"MainTabName": "Last Visit",
"MainTabData": {
"subTabs": [
{
"TabId": 1,
"TabName": "PSS Score",
"TabRow": 1,
"TabCol": 1
},
{
"TabId": 2,
"TabName": "SOS Actual",
"TabRow": 1,
"TabCol": 2
},
{
"TabId": 3,
"TabName": "SOS Compliance",
"TabRow": 1,
"TabCol": 3
},
{
"TabId": 4,
"TabName": "OSA",
"TabRow": 1,
"TabCol": 4
},
{
"TabId": 5,
"TabName": "Asset",
"TabRow": 1,
"TabCol": 5
},
{
"TabId": 6,
"TabName": "Promotion",
"TabRow": 1,
"TabCol": 6
}
],
"graphDetails": [
{
"TabId": 1,
"GraphId": 1,
"GraphType": "ScoreCard",
"GraphTitle": "PSS Score",
"GraphUrl": "https://dax.parinaam.in/execute/dabur/lsv/PSS_Score_LSV_Perc",
"GraphBackground": "#F4EAFF",
"GraphOptions": {}
},
{
"TabId": 1,
"GraphId": 2,
"GraphType": "ScoreCard",
"GraphTitle": "SOS Actual",
"GraphUrl": "https://dax.parinaam.in/execute/dabur/lsv/sos_actual_lsv_perc",
"GraphBackground": "#FFF3ED",
"GraphOptions": {}
},
{
"TabId": 1,
"GraphId": 3,
"GraphType": "ScoreCard",
"GraphTitle": "SOS Compliance",
"GraphUrl": "https://dax.parinaam.in/execute/dabur/lsv/sos_compliance_lsv_perc",
"GraphBackground": "#FFFEF0",
"GraphOptions": {}
},
{
"TabId": 1,
"GraphId": 4,
"GraphType": "ScoreCard",
"GraphTitle": "OSA",
"GraphUrl": "https://dax.parinaam.in/execute/dabur/lsv/osa_lsv_perc",
"GraphBackground": "#ECFFFA",
"GraphOptions": {}
},
{
"TabId": 1,
"GraphId": 5,
"GraphType": "ScoreCard",
"GraphTitle": "Asset",
"GraphUrl": "https://dax.parinaam.in/execute/dabur/lsv/asset_lsv_perc",
"GraphBackground": "#EBECFF",
"GraphOptions": {}
},
{
"TabId": 1,
"GraphId": 6,
"GraphType": "ScoreCard",
"GraphTitle": "Promotion",
"GraphUrl": "https://dax.parinaam.in/execute/dabur/lsv/promotion_lsv_perc",
"GraphBackground": "#FFFEF0",
"GraphOptions": {}
},
{
"TabId": 2,
"GraphId": 1,
"GraphType": "ScoreCard",
"GraphTitle": "SOS Actual",
"GraphUrl": "https://dax.parinaam.in/execute/dabur/lsv/sos_actual_lsv_perc",
"GraphBackground": "#ECFFFA",
"GraphOptions": {},
"clickable": 1,
"DetailsPage": [
{
"TabId": 2,
"GraphId": 1,
"GraphType": "Table",
"GraphTitle": "SOS Actual - Category",
"GraphUrl": "https://dax.parinaam.in/execute/dabur/detlsv/sos_actual_lsv_perc_on_category",
"GraphBackground": "#ECFFFA",
"GraphOptions": {}
}
]
},
{
"TabId": 2,
"GraphId": 2,
"GraphType": "BarGraph",
"GraphTitle": "SOS Actual Trend",
"GraphUrl": "https://dax.parinaam.in/execute/dabur/kpilsv/sos_actual_trend_lsv_perc",
"GraphBackground": "#F4EAFF",
"GraphOptions": {
"axisX": "month",
"axisY": "score",
"labelShow": 1,
"barColors": [
"#11a4ff",
"#0ea3e3",
"#0b9ddb"
],
"gridLinesH": 1,
"gridLinesV": 1
}
},
{
"TabId": 3,
"GraphId": 3,
"GraphType": "ScoreCard",
"GraphTitle": "SOS Compliance",
"GraphUrl": "https://dax.parinaam.in/execute/dabur/lsv/sos_compliance_lsv_perc",
"GraphBackground": "#ECFFFA",
"GraphOptions": {},
"clickable": 1,
"DetailsPage": [
{
"TabId": 3,
"GraphId": 3,
"GraphType": "Table",
"GraphTitle": "SOS Actual - Category",
"GraphUrl": "https://dax.parinaam.in/execute/dabur/detlsv/sos_compliance_lsv_perc_on_category",
"GraphBackground": "#ECFFFA",
"GraphOptions": {}
}
]
},
{
"TabId": 3,
"GraphId": 4,
"GraphType": "BarGraph",
"GraphTitle": "SOS Compliance Trend",
"GraphUrl": "https://dax.parinaam.in/execute/dabur/kpilsv/sos_compliance_trend_lsv_perc",
"GraphBackground": "#fff",
"GraphOptions": {
"axisX": "month",
"axisY": "score",
"labelShow": 1,
"barColors": [
"#11a4ff",
"#0ea3e3",
"#0b9ddb"
],
"gridLinesH": 1,
"gridLinesV": 1
}
},
{
"TabId": 5,
"GraphId": 5,
"GraphType": "ScoreCard",
"GraphTitle": "Asset",
"GraphUrl": "https://dax.parinaam.in/execute/dabur/lsv/asset_lsv_perc",
"GraphBa ckground": "#ECFFFA",
"GraphOptions": {},
"clickable": 1,
"DetailsPage": [
{
"TabId": 5,
"GraphId": 5,
"GraphType": "Table",
"GraphTitle": "Asset Availability",
"GraphUrl": "https://dax.parinaam.in/execute/dabur/kpilsv/asset_availability_lsv",
"GraphBackground": "#ECFFFA",
"GraphOptions": {}
},
{
"TabId": 5,
"GraphId": 5,
"GraphType": "Table",
"GraphTitle": "Asset",
"GraphUrl": "https://dax.parinaam.in/execute/dabur/kpilsv/additional_visibility_lsv",
"GraphBackground": "#ECFFFA",
"GraphOptions": {}
}
]
},
{
"TabId": 5,
"GraphId": 6,
"GraphType": "Table",
"GraphTitle": "Asset Details",
"GraphUrl": "https://dax.parinaam.in/execute/dabur/kpilsv/asset_trend_lsv_perc",
"GraphBackground": "#ECFFFA",
"GraphOptions": {}
},
{
"TabId": 4,
"GraphId": 7,
"GraphType": "ScoreCard",
"GraphTitle": "OSA",
"GraphUrl": "https://dax.parinaam.in/execute/dabur/lsv/osa_lsv_perc",
"GraphBackground": "#ECFFFA",
"GraphOptions": {},
"clickable": 1,
"DetailsPage": [
{
"TabId": 4,
"GraphId": 7,
"GraphType": "Table",
"GraphTitle": "SOS Actual - Category",
"GraphUrl": "https://dax.parinaam.in/execute/dabur/detlsv/osa_lsv_perc_on_category",
"GraphBackground": "#ECFFFA",
"GraphOptions": {}
}
]
},
{
"TabId": 4,
"GraphId": 8,
"GraphType": "PieChart",
"GraphTitle": "OSA",
"GraphUrl": "https://dax.parinaam.in/execute/dabur/kpilsv/osa_trend_lsv_perc",
"GraphBackground": "#ECFFFA",
"GraphOptions": {}
},
{
"TabId": 6,
"GraphId": 9,
"GraphType": "ScoreCard",
"GraphTitle": "Promotion",
"GraphUrl": "https://dax.parinaam.in/execute/dabur/lsv/promotion_lsv_perc",
"GraphBackground": "#ECFFFA",
"GraphOptions": {},
"clickable": 1,
"DetailsPage": [
{
"TabId": 6,
"GraphId": 9,
"GraphType": "Table",
"GraphTitle": "Promotion Availability",
"GraphUrl": "https://dax.parinaam.in/execute/dabur/kpilsv/promotion_availability_lsv",
"GraphBackground": "#ECFFFA",
"GraphOptions": {}
}
]
},
{
"TabId": 6,
"GraphId": 10,
"GraphType": "BarGraph",
"GraphTitle": "Promotion",
"GraphUrl": "https://dax.parinaam.in/execute/dabur/kpilsv/promotion_trend_lsv_perc",
"GraphBackground": "#ECFFFA",
"GraphOptions": {}
}
]
}
}
]
}
+343
View File
@@ -0,0 +1,343 @@
import { Platform, StyleSheet } from 'react-native';
import { GlobalTheme, Screen } from '../../../theme';
export const styles = StyleSheet.create({
container: {
flex: 1,
// backgroundColor: GlobalTheme.colors.white,
paddingHorizontal: 10,
// justifyContent:'center'
},
logoContainer: {
alignItems: 'center',
},
appLogo: {
height: 200,
width: 200,
resizeMode: 'contain',
},
appMaskLogo: {
resizeMode: 'stretch',
width: Screen.screenHeight * 0.9,
height: Screen.screenHeight * 0.33,
},
titleContainer: {
alignSelf: 'center',
// marginTop: 15,
},
titleText: {
fontSize: 25,
fontWeight: 'bold',
color: GlobalTheme.colors.black,
},
subtitleText: {
fontSize: 15,
color: '#1F2128',
marginBottom: 14,
fontWeight: '600',
alignSelf: 'center',
width: '80%',
textAlign: 'center',
},
subtitleHighlight: {
color: '#2381E9',
},
headerStyle: {
height: '8%',
backgroundColor: '#113F8C',
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
paddingHorizontal: 15
},
headerText: {
color: GlobalTheme.colors.white,
fontSize: GlobalTheme.typography.fontSize.medium,
fontWeight: '600'
},
headerDotBtn: {
backgroundColor: '#295398',
flexDirection: 'row',
paddingHorizontal: 5,
alignItems: 'center',
borderRadius: 10
},
selectCard: {
backgroundColor: '#FFFFFF',
borderRadius: 30,
elevation: 7,
padding: 15,
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
marginTop: 15,
marginHorizontal: 15,
borderWidth: 1.5,
borderColor: '#C9DAFF'
},
iconStyle: {
height: 25,
width: 25,
},
header: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
margin: 15,
},
dropdown: {
flex: 1,
borderWidth: 1,
borderColor: '#ccc',
borderRadius: 8,
paddingHorizontal: 10,
marginHorizontal: 4,
height: 45,
},
row: {
margin: 10,
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center'
},
inputStyle: {
paddingHorizontal: 7,
width: '100%',
color: '#000'
// backgroundColor:'red'
},
searchBox: {
height: 45,
borderWidth: 1,
borderColor: '#ccc',
borderRadius: 10,
marginTop: 10,
flexDirection: 'row',
alignItems: 'center',
paddingHorizontal: 10
},
storeNameText: {
color: '#000',
fontWeight: '500',
fontSize: 16
},
storeCard: {
backgroundColor: '#fff',
borderWidth: 1,
borderColor: '#DFDFDF',
marginTop: 10,
padding: 10,
paddingHorizontal: 12,
borderRadius: 20,
// elevation: 2
},
cardTextBold: {
fontSize: 16,
color: '#000',
fontWeight: '500',
flexWrap: 'wrap',
width: '72%'
},
cardText: {
marginTop: 2,
fontSize: 14,
color: '#494949',
fontWeight: '400'
},
dropHeaderText: {
width: '33%',
color: '#000',
fontWeight: '400',
fontSize: 14
},
selectStoreText: {
fontSize: 15,
color: '#808CA3',
fontWeight: '400',
alignSelf: 'center',
marginTop: 5
},
storeText: {
color: '#000',
fontSize: 15,
fontWeight: '500',
marginLeft: 10
},
todayStoreText: {
marginLeft: 10,
color: '#2357C6',
fontSize: 16,
fontWeight: '500'
},
storeGredient: {
flex: 1,
padding: 10,
marginTop: 15,
borderTopLeftRadius: 25,
borderTopRightRadius: 25
},
todayStoreCard: {
flex: 1,
// width:'95%',
backgroundColor: '#fff',
borderRadius: 25,
paddingBottom: 10,
marginRight: Platform.OS === 'ios' ? 20 : 0,
// width:'100%'
},
mainContainer: {
flex: 1,
},
selectedStoreText: {
width: '75%',
borderWidth: 1.5,
borderColor: '#D8E3F1',
borderRadius: 10,
padding: 7,
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
},
filterIcon: {
backgroundColor: '#D8E3F1',
padding: 3,
borderRadius: 5
},
storeInfoText: {
color: '#000',
fontWeight: '500',
fontSize: 16,
textDecorationLine: 'underline'
},
percentBox: {
width: '48%',
height: 100,
minHeight: 100,
backgroundColor: '#EAF1FF',
borderRadius: 20,
elevation: 2,
justifyContent: 'center',
alignItems: 'center',
},
boxText: {
color: '#000',
fontWeight: '400',
fontSize: 16
},
yearLable: {
color: '#000',
fontSize: 14,
fontWeight: '500'
},
yearText: {
color: '#7F83AB',
fontSize: 14,
},
yearView: {
borderWidth: 2,
borderColor: '#D8E3F1',
borderRadius: 15,
paddingHorizontal: 10,
padding: 5,
width: '28%',
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between'
},
yearDropDown: {
height: 40,
borderWidth: 2,
borderColor: '#D8E3F1',
width: 100,
borderRadius: 20
},
btnbg: {
// marginLeft: 10,
backgroundColor: GlobalTheme.colors.secondary,
borderRadius: GlobalTheme.borderRadius.lgg,
padding: 18,
paddingVertical: 8
},
btntext: {
color: GlobalTheme.colors.white,
fontSize: GlobalTheme.typography.fontSize.xsmall,
fontWeight: GlobalTheme.typography.fontWeight.medium
},
seperator: {
marginTop: 10,
borderBottomWidth: 2,
borderColor: '#E4E4E4'
},
dropdownIcon: {
height: 12,
width: 12,
resizeMode: 'contain',
tintColor: 'gray'
},
mtdTab: {
borderBottomWidth: 2,
borderColor: '#113F8C',
width: '50%',
padding: 10,
alignItems: 'center',
// borderWidth:0.5,
},
mtdTabText: {
fontSize: 15,
color: '#000',
fontWeight: '600'
},
subTabView: {
backgroundColor: '#EAEAEA',
paddingVertical: 10,
paddingHorizontal: 5,
flexDirection: 'row',
alignItems: 'center'
},
subTab: {
backgroundColor: '#113F8C',
padding: 5,
paddingHorizontal: 10,
borderRadius: 10,
marginRight: 10
},
tabText: {
color: '#fff',
fontSize: 14,
fontWeight: '400',
},
floatingBtn: {
alignSelf: 'center',
position: 'absolute',
bottom: 10,
backgroundColor: 'rgba(255, 255, 255, 0.4)',
// backgroundColor:'#fff',
padding: 10,
paddingHorizontal: 20,
borderRadius: 30,
// elevation: 2,
zIndex: 999,
borderWidth: 1,
borderColor: '#DFDFDF'
},
modalOverlay: {
flex: 1,
// backgroundColor: 'transparent',
backgroundColor: 'rgba(87, 79, 79, 0.2)'
},
modalContainer: {
position: 'absolute',
top: Platform.OS === 'ios' ? 100 : 50,
right: 10,
width: '50%',
backgroundColor: 'white',
borderRadius: 8,
elevation: 8,
shadowColor: '#000',
shadowOpacity: 0.2,
shadowRadius: 5,
shadowOffset: { width: 0, height: 2 },
},
});
@@ -0,0 +1,659 @@
import React, { useState, useEffect } from 'react';
import { View, Text, ScrollView, TouchableOpacity, Platform, Keyboard } from 'react-native';
import { KeyboardAwareScrollView } from 'react-native-keyboard-aware-scroll-view';
import Icon from 'react-native-vector-icons/FontAwesome';
import Ionicons from 'react-native-vector-icons/Ionicons';
import mockDataFlat1 from '../data.json';
import * as RNFS from 'react-native-fs';
import moment from 'moment';
import CustomHeader from '../../../../components/CustomHeader';
import IMAGES from '../../../../constants/Images';
import CustomButton from '../../../../components/CustomButton';
import { styles } from './style';
import { GlobalTheme } from '../../../../theme';
import db, { getAllFromTable } from '../../../../constants/database';
import { UploadData2, UploadImagesWithoutWait } from '../../../../constants/uploadData';
import { getAllFileForAFolder, getDownloadJson } from '../../../../constants/function';
import { common_ImagePath, FeedbackImagesFolderPath } from '../../../../constants/constant';
import { toastError } from '../../../../constants/Toast';
import { SafeAreaView } from 'react-native-safe-area-context';
import Loader from '../../../../constants/Loader';
import { ConfirmSaveAlert } from '../../../../components/Alert';
import { useSelector } from 'react-redux';
// image upload url
const imageUploadURL = "https://di1.parinaam.in/webservice/Imageupload.asmx/Uploadimages";
const FeedbackCategories = ({ navigation, route }) => {
let user_exist_data = useSelector(state => state?.user);
const userId = user_exist_data.UserId || '';
const storeData = route?.params?.storeData;
console.log("user_exist_data===", (user_exist_data.UserId, storeData?.StoreId));
const [completedCategories, setCompletedCategories] = useState([]);
const [uniqueCategories, setUniqueCategories] = useState([]);
const [loading, setLoading] = useState(false);
const [loadingdownload, setLoadingDownload] = useState(false);
const [loadingupload, setLoadingUpload] = useState(false);
const [showAlert, setShowAlert] = useState(false);
// get data
// Insert fallback mock data if empty
const insertMasterSurveyQuestion = (data_arr = []) => {
return new Promise((resolve, reject) => {
if (!Array.isArray(data_arr) || data_arr.length === 0) {
resolve(true);
return;
}
const escape = str => (str || '').toString().replace(/'/g, "''");
const values = data_arr.map(item => {
const {
MenuId, SurveyId, SurveyName, CategoryId, Category, CategorySequence,
QuestionId, Question, QuestionType, QuestionImageAllow, QEnable,
LengthValidation, MinLength, MaxLength, OTP, DateRange, QuestionSequence,
AnswerId, Answer, ImageAllow1, ImageAllow2, Image1Mandatory, Image2Mandatory,
QuestionImageMandatory, EnableQuestion, DisableQuestion, AnswerSequence,
ShowCat, SubCategoryId, SubCategory, SubCategorySequence,
QuestionTypeNew, CalFormula, RefImage, QuestionRefImage
} = item;
return `(
'${MenuId}', '${SurveyId}', '${escape(SurveyName)}', '${CategoryId}', '${escape(Category)}',
'${CategorySequence}', '${QuestionId}', '${escape(Question)}', '${escape(QuestionType)}',
'${QuestionImageAllow ? 1 : 0}', '${QEnable ? 1 : 0}', '${LengthValidation ? 1 : 0}',
'${MinLength}', '${MaxLength}', '${OTP ? 1 : 0}', '${escape(DateRange)}',
'${QuestionSequence}', '${AnswerId}', '${escape(Answer)}',
'${ImageAllow1 ? 1 : 0}', '${ImageAllow2 ? 1 : 0}', '${Image1Mandatory ? 1 : 0}',
'${Image2Mandatory ? 1 : 0}', '${QuestionImageMandatory ? 1 : 0}',
'${escape(EnableQuestion)}', '${escape(DisableQuestion)}', '${AnswerSequence}',
'${ShowCat ? 1 : 0}', '${SubCategoryId}', '${escape(SubCategory)}',
'${SubCategorySequence}', '${escape(QuestionTypeNew)}', '${escape(CalFormula)}',
'${escape(RefImage)}', '${escape(QuestionRefImage)}'
)`;
});
const sql = `
INSERT INTO FeedbackCategory (
MenuId, SurveyId, SurveyName, CategoryId, Category, CategorySequence,
QuestionId, Question, QuestionType, QuestionImageAllow, QEnable,
LengthValidation, MinLength, MaxLength, OTP, DateRange, QuestionSequence,
AnswerId, Answer, ImageAllow1, ImageAllow2, Image1Mandatory, Image2Mandatory,
QuestionImageMandatory, EnableQuestion, DisableQuestion, AnswerSequence,
ShowCat, SubCategoryId, SubCategory, SubCategorySequence,
QuestionTypeNew, CalFormula, RefImage,QuestionRefImage
) VALUES ${values.join(',')}
`;
db.transaction(tx => {
tx.executeSql(`DELETE FROM FeedbackCategory`, [], () => {
tx.executeSql(sql, [], () => {
console.log('FeedbackCategory inserted successfully');
resolve(true);
}, (err) => {
console.log('Insert error in FeedbackCategory:', err);
reject(err);
});
}, (err) => {
console.log('Delete error before insert FeedbackCategory:', err);
reject(err);
});
});
});
};
async function getData() {
setLoadingDownload(true);
try {
const data1 = {
Downloadtype: "Master_SurveyQuestion_storeDNA",
Param1: storeData?.StoreId || '',
Param2: "",
username: user_exist_data?.UserId || '',
};
const url = "https://di1.parinaam.in/Webservice/GenericService.svc/DownloadJson";
console.log("Downloading from:", url, data1);
const res = await getDownloadJson(url, data1);
console.log('Response:', res);
if (res?.Master_SurveyQuestion_storeDNA?.length) {
const data = res.Master_SurveyQuestion_storeDNA || [];
console.log("Inserting downloaded data into FeedbackCategory...");
const all = await getAllFromTable('FeedbackCategory');
// if (all?.length === 0) {
console.log("Table empty. Inserting fallback mock data...");
await insertMasterSurveyQuestion(data);
setLoadingDownload(false);
// } else {
// console.log("FeedbackCategory table already has data.");
// setLoadingDownload(false);
// }
} else {
console.log("No Master_SurveyQuestion_storeDNA data found in response");
setLoadingDownload(false);
}
} catch (err) {
console.log("Dashboard feedback survey download error:", err);
setLoadingDownload(false);
}
}
// download data
// 1. Initial load → download and populate local DB and categories
useEffect(() => {
const init = async () => {
await getData(); // Download and insert
await loadCompleted(); // Load completion info
const data = await getUniqueCategoriesFromDB(); // Load categories
setUniqueCategories(data); // Update UI
};
init();
}, []);
// 2. Every time screen is focused → recheck completed categories and ticks
useEffect(() => {
const loadOnFocus = async () => {
await loadCompleted();
const data = await getUniqueCategoriesFromDB();
setUniqueCategories(data);
};
const unsubscribe = navigation.addListener('focus', loadOnFocus);
return unsubscribe;
}, [navigation]);
// useEffect(() => {
// const init = async () => {
// await getData(); // fetch and insert data
// await loadCompleted(); // ← added
// const data = await getUniqueCategoriesFromDB(); // ← added
// setUniqueCategories(data); // ← added
// };
// init();
// // const unsubscribe = navigation.addListener('focus', loadOnFocus);
// // return unsubscribe;
// }, [navigation]);
// end download data.
const isCompleted = (cat) => {
return completedCategories.find(
c => c?.CategoryId === cat?.CategoryId && c?.SurveyId === cat?.SurveyId
);
};
const loadCompleted = async () => {
const d2 = moment().format('MM/DD/YYYY');
console.log("loaddddd", `SELECT DISTINCT CATEGORY_ID, SURVEY_ID , IFNULL(STATUS , '') AS STATUS FROM FeedBackLocalTable WHERE STORE_ID='${storeData?.StoreId}' AND VISIT_DATE='${d2}'`)
return new Promise((resolve, reject) => {
db.transaction(tx => {
tx.executeSql(
`SELECT DISTINCT CATEGORY_ID, SURVEY_ID , IFNULL(STATUS , '') AS STATUS FROM FeedBackLocalTable WHERE STORE_ID='${storeData?.StoreId}' AND VISIT_DATE='${d2}'`,
[],
(tx, results) => {
const completed = [];
for (let i = 0; i < results.rows.length; i++) {
const row = results.rows.item(i);
completed.push({
CategoryId: row.CATEGORY_ID,
SurveyId: row.SURVEY_ID,
Status: row.STATUS
});
}
setCompletedCategories(completed);
// console.log('Completed categories from table:', completed);
resolve();
},
(tx, error) => {
console.log('DB error loading completed:', error);
reject(error);
}
);
});
});
};
const getUniqueCategoriesFromDB = () => {
return new Promise((resolve, reject) => {
db.transaction(tx => {
tx.executeSql(
`SELECT CategoryId, Category, SurveyId, MIN(QuestionSequence) AS minSeq
FROM FeedbackCategory
GROUP BY CategoryId, Category, SurveyId
ORDER BY minSeq ASC`,
[],
(tx, results) => {
const unique = [];
for (let i = 0; i < results.rows.length; i++) {
const row = results.rows.item(i);
unique.push({
CategoryId: row.CategoryId,
Category: row.Category,
SurveyId: row.SurveyId
});
}
resolve(unique);
},
(tx, error) => {
console.log('Error fetching distinct categories:', error);
reject(error);
}
);
});
});
};
// upload data
async function Upload_Data() {
// await saveData("P");
setLoadingUpload(true);
const UserId = userId;
const UploadKeyName = 'Sales_DNA_FeedBack';
const MID = 0;
let postData = {};
let allChilds = [];
const d2 = moment().format('MM/DD/YYYY');
await new Promise((resolve, reject) => {
db.transaction(async function (txn) {
let query = `SELECT * FROM FeedBackLocalTable WHERE STORE_ID='${storeData?.StoreId}' AND VISIT_DATE='${d2}'`;
console.log("query=====", query)
await txn.executeSql(
query,
[],
async (txn2, txnres2) => {
// console.log("Records found:", txnres2.rows.length);
if (txnres2.rows.length > 0) {
for (let i = 0; i < txnres2.rows.length; i++) {
let data = txnres2.rows.item(i);
let {
SURVEY_ID, CATEGORY_ID, MaxLength, MinLength, QuestionImageAllow, QUESTION,
QUESTION_ID, QUESTION_TYPE, OTP, ANSWER, ANSWER_ID, IMAGE_ALLOW1, IMAGE1,
IMAGE_ALLOW2, IMAGE2, MULTI_OPTIONS_IDS, IS_DISABLED, IS_ENABLED
} = data;
let renamedObj = {
SurveyId: SURVEY_ID,
CategoryId: CATEGORY_ID,
QuestionId: QUESTION_ID,
QuestionType: QUESTION_TYPE,
Answer: ANSWER,
AnswerId: ANSWER_ID,
MultiOptionsIds: MULTI_OPTIONS_IDS,
Image1: IMAGE1,
Image2: IMAGE2,
ImageAllow1: IMAGE_ALLOW1,
ImageAllow2: IMAGE_ALLOW2
};
// Fix: IS_DISABLED is expected to be 0 or 1
let isQtnEnabled = JSON.parse(IS_DISABLED)[QUESTION_ID]
let obj = {};
if (isQtnEnabled) {
obj['MID'] = 0;
obj['UserId'] = userId;
// obj['Store_Id'] = '';
// obj['MenuId'] = '';
obj['SurveyId'] = renamedObj?.SurveyId;
obj['CategoryId'] = renamedObj?.CategoryId;
obj['QuestionId'] = renamedObj?.QuestionId;
obj['QuestionType'] = renamedObj?.QuestionType;
obj['Answer'] = renamedObj?.Answer;
obj['AnswerId'] = renamedObj?.AnswerId;
obj['MultiAnswerId'] = (renamedObj?.MultiOptionsIds != null && renamedObj?.MultiOptionsIds != 'undefined') ? renamedObj?.MultiOptionsIds : '';
obj['AnswerImage1'] = renamedObj?.Image1;
obj['AnswerImage2'] = renamedObj?.Image2;
allChilds.push(obj);
}
}
postData['MID'] = MID;
postData['Keys'] = UploadKeyName || 'Sales_DNA_FeedBack';
postData['JsonData'] = JSON.stringify(allChilds);
postData['UserId'] = UserId;
resolve({ PostData: postData });
} else {
resolve({ PostData: postData });
}
},
(txnE, txnerr) => {
// console.log('Query error:', txnerr);
resolve({ PostData: postData });
}
);
});
}).catch((err) => {
console.log("Transaction error:", err);
});
// console.log("Preparing to upload data...");
const url = 'https://di1.parinaam.in/Webservice/GenericService.svc/UploadJson';
console.log("URL:", url);
console.log("PostData:", postData);
let allFilesToUpload = await getAllFileForAFolder(FeedbackImagesFolderPath, "Store_DNA");
console.log('All files to upload:', allFilesToUpload);
await uploadImagesFormFiles12(allFilesToUpload);
await UploadData2(url, postData)
.then(async (res) => {
// console.log('Upload Response:', res);
if (res?.UploadJsonResult === "Success") {
// console.log("Upload successful.");
try {
await updateAllUploadStatuses(d2);
await loadCompleted(); // Refresh the UI with updated STATUS
} catch (err) {
console.log("Failed to update status after upload:", err);
}
setLoadingUpload(false);
setShowAlert(false);
} else {
console.log("Upload failed.");
setLoadingUpload(false);
}
})
.catch((err) => {
console.log("Upload error:", err);
setLoadingUpload(false);
});
}
async function uploadImagesFormFiles12(allfiles = []) {
// console.log("allfiles===========", allfiles);
let d1 = new Date();
let d2 = moment(d1).format('MM/DD/YYYY');
let uploadCount = 0;
const url = imageUploadURL;
var isAllUploaded = false;
return await Promise.all(
allfiles.map(async (file, i) => {
let index = i;
let actualfilepath = 'file://' + file.uri;
// console.log("actualfilepath==", actualfilepath);
let isExists = await RNFS.exists(actualfilepath).then((res) => { return res; });
console.log('isExists file', isExists, file.uri, file);
let isImageUploaded = false;
// isExists==false
if (isExists) {
let postData = new FormData();
postData.append('file', {
uri: actualfilepath,
type: file.type,
name: file.name,
});
postData.append('Foldername', "AddNewStoreImages");
postData.append('Path', d2);
isImageUploaded = await UploadImagesWithoutWait(postData, url);
if (isImageUploaded == true) {
// console.log('isExists file uploaded', isExists, uploadCount, index, allfiles.length - 1);
await RNFS.unlink(actualfilepath);
uploadCount++;
// UpdateUploadCountLocal(uploadCount+1)
if (uploadCount == allfiles.length) {
isAllUploaded = true;
}
} else {
console.log('file not uploaded:', isExists, actualfilepath);
}
}
else {//file does not exists
uploadCount++;
// UpdateUploadCountLocal(uploadCount+1)
if (uploadCount == allfiles.length) {
isAllUploaded = true;
}
}
return isImageUploaded;
})
).then((val) => {
console.log(val);
return isAllUploaded;
})
.catch((err) => {
console.log(err);
return false;
})
}
// end upload image
const areAllCategoriesCompleted = () => {
return uniqueCategories?.every(cat => isCompleted(cat));
};
const areAllCategoriesUploaded = () => {
return uniqueCategories?.every(cat => {
const completed = isCompleted(cat);
return completed?.Status === 'U';
});
};
async function onSubmitData() {
// let isvalid = await validate();
if (areAllCategoriesCompleted()) {
Keyboard.dismiss();
setShowAlert(true);
} else {
toastError("Alert", "Please fill all categories.");
}
}
function onSaveCancel() {
setShowAlert(false);
}
// update status of uploading data
const updateFeedbackStatusToUploaded = async (visitDate) => {
return new Promise((resolve, reject) => {
db.transaction(tx => {
tx.executeSql(
`UPDATE FeedBackLocalTable SET STATUS = 'U' WHERE STORE_ID=? and VISIT_DATE = ?`,
[storeData?.StoreId, visitDate],
(_, result) => {
// console.log("Local STATUS updated to 'U' after upload.");
resolve(result);
},
(tx, error) => {
console.log("Error updating STATUS locally:", error);
reject(error);
}
);
});
});
};
const updateUploadStatusForStores = (visitDate) => {
return new Promise((resolve, reject) => {
db.transaction(tx => {
// First: Set uploaded stores to 'U' for the given visit date
tx.executeSql(
`
UPDATE StoreInfoDNALocal
SET UPLOAD_STATUS = 'U'
WHERE EXISTS (
SELECT 1
FROM FeedBackLocalTable
WHERE FeedBackLocalTable.STORE_ID = StoreInfoDNALocal.StoreId
AND FeedBackLocalTable.STATUS = 'U'
AND FeedBackLocalTable.VISIT_DATE = ?
)
AND EXISTS (
SELECT 1
FROM FeedBackLocalTable
WHERE FeedBackLocalTable.STORE_ID = StoreInfoDNALocal.StoreId
AND FeedBackLocalTable.VISIT_DATE = ?
)
`,
[visitDate, visitDate],
() => {
console.log('Uploaded stores marked as U');
// Second: Set non-uploaded stores to 'P' for the given visit date
tx.executeSql(
`
UPDATE StoreInfoDNALocal
SET UPLOAD_STATUS = 'P'
WHERE EXISTS (
SELECT 1
FROM FeedBackLocalTable
WHERE FeedBackLocalTable.STORE_ID = StoreInfoDNALocal.StoreId
AND FeedBackLocalTable.VISIT_DATE = ?
)
AND NOT EXISTS (
SELECT 1
FROM FeedBackLocalTable
WHERE FeedBackLocalTable.STORE_ID = StoreInfoDNALocal.StoreId
AND FeedBackLocalTable.STATUS = 'U'
AND FeedBackLocalTable.VISIT_DATE = ?
)
`,
[visitDate, visitDate],
() => {
console.log('Pending stores marked as P');
resolve(true);
},
(tx, error) => {
console.log('Error updating pending stores:', error);
reject(error);
}
);
},
(tx, error) => {
console.log('Error updating uploaded stores:', error);
reject(error);
}
);
});
});
};
const updateAllUploadStatuses = async (visitDate) => {
try {
await updateFeedbackStatusToUploaded(visitDate);
await updateUploadStatusForStores(visitDate);
console.log('All upload statuses updated successfully');
} catch (error) {
console.log('Error in updating upload statuses:', error);
}
};
return (
<KeyboardAwareScrollView style={{ flex: 1 }} contentContainerStyle={{ justifyContent: 'center', flex: 1 }}>
<SafeAreaView style={{ flex: 1, backgroundColor: GlobalTheme.colors.primary }}>
<View style={{ flex: 1, backgroundColor: GlobalTheme.colors.white }}>
<CustomHeader
title="Feedback Categories"
// rightIcon={IMAGES.menuIcon}
// onRightPress={() => navigation.navigate('Dashboard')}
onLeftPress={() => navigation.goBack()}
leftIcon={IMAGES.backIcon}
/>
<Loader visible={loading} loadingtext={'Loading ...'} />
<Loader visible={loadingdownload} loadingtext={'data downloading ...'} />
<Loader visible={loadingupload} loadingtext={'Uploading Data...'} />
<ScrollView style={{ flex: 1 }} contentContainerStyle={{ paddingBottom: 10 }}>
<View style={styles.container}>
{uniqueCategories?.map((cat, index) => {
const completedEntry = isCompleted(cat);
{/* console.log("completedEntry", completedEntry); */ }
const isUploaded = completedEntry?.Status === 'U';
{/* console.log("isUploaded", isUploaded); */ }
return (
<TouchableOpacity
key={index}
disabled={isUploaded} // 👈 disable if already uploaded
style={[styles.cardview, styles.categoryItem]} // 👈 visually indicate disabled
onPress={() => {
if (!isUploaded) {
navigation.navigate('Feedback', {
CategoryId: cat?.CategoryId,
SurveyId: cat?.SurveyId,
CategoryName: cat?.Category,
storeData: storeData
});
}
}}
>
<View style={styles.circleIcon}>
<Text style={styles.circleText}>{cat.Category?.charAt(0)}</Text>
</View>
<View style={{ flex: 1 }}>
<Text style={styles.categoryName}>{cat.Category}</Text>
</View>
{isUploaded ? (
<Ionicons name="checkmark-done" size={22} color="green" />
) : completedEntry ? (
<Icon name="check-circle" size={22} color="green" />
) : null}
</TouchableOpacity>
);
})}
{!loading && uniqueCategories.length === 0 && (
<Text style={{ textAlign: 'center', marginTop: 20 }}>No categories available.</Text>
)}
</View>
</ScrollView>
<CustomButton
onPress={() => {
if (areAllCategoriesUploaded()) return;
onSubmitData();
}}
title={areAllCategoriesUploaded() ? 'Data Uploaded' : 'Upload & Save'}
style={areAllCategoriesUploaded() ? styles.btnbg1 : styles.btnbg}
textstyle={areAllCategoriesUploaded() ? styles.btntext1 : styles.btntext}
disabled={areAllCategoriesUploaded()}
/>
<ConfirmSaveAlert
showAlert={showAlert}
onCancelCallBack={onSaveCancel}
onYesCallBack={Upload_Data}
msg="Do you really want to upload data?"
/>
</View>
</SafeAreaView>
</KeyboardAwareScrollView>
);
};
export default FeedbackCategories;
@@ -0,0 +1,66 @@
import { StyleSheet } from 'react-native';
import { GlobalTheme } from '../../../../theme';
export const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: GlobalTheme.colors.white,
paddingHorizontal: 10,
paddingVertical: 10
},
btnbg: {
backgroundColor: GlobalTheme.colors.secondary, borderRadius: GlobalTheme.borderRadius.md, marginVertical: 10, marginHorizontal: 10, bottom:0
},
btntext: {
color: GlobalTheme.colors.white,
fontSize: GlobalTheme.typography.fontSize.medium
},
btnbg1: {
backgroundColor: GlobalTheme.colors.gray, borderRadius: GlobalTheme.borderRadius.md, marginVertical: 10, marginHorizontal: 10, bottom:0 , opacity : 0.5
},
btntext1: {
color: GlobalTheme.colors.white,
fontSize: GlobalTheme.typography.fontSize.medium
},
cardview: {
borderColor: GlobalTheme.colors.lightblue, borderWidth: 1, borderRadius: GlobalTheme.borderRadius.sm, padding: 8, backgroundColor: GlobalTheme.colors.lightblue, marginVertical: 10
},
categoryName: {
fontSize: GlobalTheme.typography.fontSize.small, fontWeight: GlobalTheme.typography.fontWeight.medium, color: GlobalTheme.colors.black, marginVertical: 10
},
//
categoryItem: {
flexDirection: 'row',
alignItems: 'center',
paddingVertical: 12,
paddingHorizontal: 10,
backgroundColor: '#f6f8fd',
borderRadius: 10,
marginVertical: 6,
elevation: 1,
},
circleIcon: {
width: 36,
height: 36,
borderRadius: 18,
backgroundColor: '#dce6f9',
justifyContent: 'center',
alignItems: 'center',
marginRight: 12,
},
circleText: {
fontSize: 16,
fontWeight: 'bold',
color: '#4c6ef5',
},
categoryName: {
fontSize: 15,
fontWeight: '600',
color: '#333',
},
});
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,333 @@
import React, { useState, useEffect } from 'react';
import {
View,
Text,
TextInput,
ScrollView,
TouchableOpacity,
Platform,
Image,
Modal,
} from 'react-native';
import AsyncStorage from '@react-native-async-storage/async-storage';
import ModalSelector from 'react-native-modal-selector';
import MultiSelect from 'react-native-multiple-select';
import CustomHeader from '../../../components/CustomHeader';
import IMAGES from '../../../constants/Images';
import { GlobalTheme } from '../../../theme';
import CustomButton from '../../../components/CustomButton';
import { styles } from './style';
import { toastError, toastSuccess } from '../../../constants/Toast';
import { KeyboardAwareScrollView } from 'react-native-keyboard-aware-scroll-view';
import Icon from 'react-native-vector-icons/FontAwesome';
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
import mockDataFlat1 from './data.json';
import CustomCamera from '../../../components/CustomCamera';
import CustomModal from '../../../components/CustomModal';
import { SafeAreaView } from 'react-native-safe-area-context';
const transformSurveyData = (flatData) => {
const grouped = {};
flatData?.forEach(item => {
if (!grouped[item.QuestionId]) {
grouped[item.QuestionId] = {
QuestionId: item.QuestionId,
Question: item.Question,
QuestionType: item.QuestionTypeNew,
QEnable: item.QEnable,
EnableQuestion: item.EnableQuestion,
DisableQuestion: item.DisableQuestion,
QuestionSequence: item.QuestionSequence,
CategorySequence: item.CategorySequence,
Answers: []
};
}
if (!['Number', 'Text', 'OnlyText'].includes(item.QuestionTypeNew)) {
if (item.AnswerId || item.Answer) {
grouped[item.QuestionId].Answers.push({
AnswerId: item.AnswerId,
Answer: item.Answer
});
}
}
});
return Object.values(grouped).sort((a, b) => a.QuestionSequence - b.QuestionSequence);
};
const FeedbackFormScreen = ({ route, navigation }) => {
const { CategoryId, SurveyId, CategoryName } = route.params || {};
const [questions, setQuestions] = useState([]);
const [answers, setAnswers] = useState({});
const [enabledQuestions, setEnabledQuestions] = useState({});
const [showCamera, setShowCamera] = useState(false);
const [activeCameraQId, setActiveCameraQId] = useState(null);
// For modal
const [showModal, setShowModal] = useState(false);
const [selectedImg, setSelectedImg] = useState(null);
const [reCapImgModalObj, setReCapImgModalObj] = useState({});
useEffect(() => {
const filtered = mockDataFlat1?.mockDataFlat?.filter(
item => item.CategoryId === CategoryId && item.SurveyId === SurveyId
);
const transformed = transformSurveyData(filtered);
setQuestions(transformed);
const enableMap = {};
transformed.forEach(q => {
enableMap[q.QuestionId] = q.QEnable;
});
setEnabledQuestions(enableMap);
loadSavedAnswers();
}, []);
const loadSavedAnswers = async () => {
try {
const saved = await AsyncStorage.getItem('feedback_answers');
if (saved) setAnswers(JSON.parse(saved));
} catch (e) {
console.warn('Failed to load saved answers');
}
};
const saveAnswers = async () => {
try {
await AsyncStorage.setItem('feedback_answers', JSON.stringify(answers));
const completedRaw = await AsyncStorage.getItem('completed_categories');
let completed = completedRaw ? JSON.parse(completedRaw) : [];
if (!completed.includes(CategoryId)) {
completed.push(CategoryId);
await AsyncStorage.setItem('completed_categories', JSON.stringify(completed));
}
toastSuccess('Success', 'Answers Saved Successfully');
navigation.goBack();
} catch (e) {
toastError('Error', 'Failed to save answers');
}
};
const handleAnswerChange = (questionId, value, enableList = '', disableList = '') => {
setAnswers(prev => ({ ...prev, [questionId]: value }));
const updated = { ...enabledQuestions };
if (disableList) {
disableList.split(',').forEach(id => {
updated[parseInt(id)] = false;
});
}
if (enableList) {
enableList.split(',').forEach(id => {
updated[parseInt(id)] = true;
});
}
setEnabledQuestions(updated);
};
const handleImageCaptured = (photo) => {
if (activeCameraQId) {
setAnswers(prev => ({ ...prev, [activeCameraQId]: photo.uri }));
setSelectedImg(photo.uri);
setShowModal(true);
}
setShowCamera(false);
setActiveCameraQId(null);
};
const openCamera = (questionId) => {
setActiveCameraQId(questionId);
setShowCamera(true);
};
const _OpenCaptureImage_Modal = (imgUri) => {
const isImageCap = imgUri !== '' && imgUri != null;
let { cameraType } = reCapImgModalObj;
return (
<CustomModal
showModal={showModal}
title={'Captured Image'}
message={''}
style={{ alignItems: 'center' }}
hideDefaultClose={true}
>
{isImageCap && (
<View style={{ alignItems: 'center' }}>
<Image
source={{ uri: selectedImg }}
style={{
width: 250,
height: 250,
marginBottom: 10,
borderRadius: 10,
}}
/>
<TouchableOpacity
style={{ marginBottom: 10 }}
onPress={() => {
setShowModal(false);
openCamera(activeCameraQId);
}}
>
<MaterialCommunityIcons name="camera-retake" size={50} color={GlobalTheme.colors.primary} />
</TouchableOpacity>
<TouchableOpacity
onPress={() => setShowModal(false)}
style={{ paddingVertical: 10, paddingHorizontal: 30, backgroundColor: GlobalTheme.colors.primary, borderRadius: 6 }}
>
<Text style={{ color: '#fff' }}>Close</Text>
</TouchableOpacity>
</View>
)}
</CustomModal>
);
};
return (
<KeyboardAwareScrollView style={{ flex: 1 }} contentContainerStyle={{ flexGrow: 1 }}>
<SafeAreaView style={{ flex: 1, backgroundColor: GlobalTheme.colors.white }}>
<CustomHeader
title="Feedback and Rating"
rightIcon={IMAGES.menuIcon}
leftIcon={IMAGES.leftArrowIcon}
onLeftPress={() => navigation.goBack()}
onRightPress={() => navigation.navigate('Dashboard')}
/>
<ScrollView>
<View style={styles.container}>
<View style={styles.cardview}>
<Text style={styles.categoryName}>{CategoryName}</Text>
{questions.map((qtn) => {
if (!enabledQuestions[qtn.QuestionId]) return null;
return (
<View key={qtn.QuestionId} style={styles.questionBlock}>
<Text style={styles.questionText}>{qtn.Question}</Text>
{qtn.QuestionType === 'Single choice list' && (
<ModalSelector
animationType='fade'
optionContainerStyle={{ backgroundColor: '#fff' }}
cancelContainerStyle={{backgroundColor: '#fff' }}
key={qtn.QuestionId}
data={qtn.Answers.map(ans => ({ key: ans.AnswerId, label: ans.Answer }))}
initValue="Select one"
onChange={option => {
const selectedAnswer = option.label;
// Look up the matching full answer entry from mockDataFlat1
const matchedRow = mockDataFlat1.mockDataFlat.find(item =>
item.QuestionId === qtn.QuestionId && item.Answer === selectedAnswer
);
const enableQ = matchedRow?.EnableQuestion || '';
const disableQ = matchedRow?.DisableQuestion || '';
handleAnswerChange(
qtn.QuestionId,
selectedAnswer,
enableQ,
disableQ
);
}}
>
<Text style={styles.selectorBox}>
{answers[qtn.QuestionId] || '---Select option---'}
</Text>
</ModalSelector>
)}
{qtn.QuestionType === 'Multi choice list' && (
<MultiSelect
items={qtn.Answers}
uniqueKey="AnswerId"
displayKey="Answer"
onSelectedItemsChange={(selected) =>
handleAnswerChange(
qtn.QuestionId,
selected,
qtn.EnableQuestion,
qtn.DisableQuestion
)
}
selectedItems={answers[qtn.QuestionId] || []}
selectText="Pick Items"
searchInputPlaceholderText="Search Items..."
searchIcon={<Icon name="search" size={18} color="#888" />}
tagRemoveIconColor="#ccc"
tagBorderColor="#ccc"
tagTextColor="#000"
selectedItemTextColor={GlobalTheme.colors.primary}
selectedItemIconColor="#000"
itemTextColor="#000"
styleMainWrapper={styles.multiSelect}
submitButtonText={'Submit'}
textColor={GlobalTheme.colors.black}
submitButtonColor={GlobalTheme.colors.secondary}
styleItemsContainer={styles.styleItemsContainer}
styleListContainer={styles.styleListContainer}
styleInputGroup={styles.styleInputGroup}
styleDropdownMenuSubsection={styles.styleDropdownMenuSubsection}
styleDropdownMenu={styles.styleDropdownMenu}
/>
)}
{(qtn.QuestionType === 'Text' || qtn.QuestionType === 'OnlyText' || qtn.QuestionType === 'Number') && (
<TextInput
style={styles.textInput}
placeholder="Enter here..."
keyboardType={(qtn.QuestionType == 'Decimal' ? 'decimal-pad' : (qtn.QuestionType == 'Text' || qtn.QuestionType == 'OnlyText' ? 'default' : 'number-pad'))}
value={answers[qtn.QuestionId] || ''}
onChangeText={(text) => handleAnswerChange(qtn.QuestionId, text)}
returnKeyType={(Platform.OS === 'ios') ? 'done' : 'next'}
/>
)}
{qtn.QuestionType === 'Image' && (
<>
<TouchableOpacity
onPress={() => {
if (answers[qtn.QuestionId]) {
setSelectedImg(answers[qtn.QuestionId]);
setReCapImgModalObj({ cameraType: '1' });
setActiveCameraQId(qtn.QuestionId);
setShowModal(true);
} else {
openCamera(qtn.QuestionId); // if no image yet, open camera directly
}
}}
>
<Image
source={answers[qtn.QuestionId] ? IMAGES.greenCameraIcon : IMAGES.redCameraIcon}
style={{ width: 40, height: 40 }}
/>
</TouchableOpacity>
</>
)}
</View>
);
})}
</View>
</View>
</ScrollView>
<CustomButton
onPress={saveAnswers}
title={'Save'}
style={styles.btnbg}
textstyle={styles.btntext}
/>
{showCamera && (
<Modal visible={showCamera} animationType="slide" presentationStyle="fullScreen">
<CustomCamera
onImageCaptured={handleImageCaptured}
onClose={() => setShowCamera(false)}
/>
</Modal>
)}
{/* Modal Preview with Retake */}
{_OpenCaptureImage_Modal(selectedImg)}
</SafeAreaView>
</KeyboardAwareScrollView>
);
};
export default FeedbackFormScreen;
+816
View File
@@ -0,0 +1,816 @@
import React, { useState, useEffect, useRef } from 'react';
import { View, Text, TextInput, ScrollView, TouchableOpacity, Platform, Image, Modal, BackHandler, Keyboard } from 'react-native';
import ModalSelector from 'react-native-modal-selector';
import MultiSelect from 'react-native-multiple-select';
import CustomHeader from '../../../components/CustomHeader';
import IMAGES from '../../../constants/Images';
import { GlobalTheme, Screen } from '../../../theme';
import CustomButton from '../../../components/CustomButton';
import { styles } from './style';
import { toastError, toastSuccess } from '../../../constants/Toast';
import { KeyboardAwareScrollView } from 'react-native-keyboard-aware-scroll-view';
import Icon from 'react-native-vector-icons/FontAwesome';
import CustomCamera from '../../../components/CustomCamera';
import db from '../../../constants/database';
import { getImage } from '../../../constants/function';
import CustomIcon from '../../../constants/IconGenerator';
import { ImageZoom } from '@likashefqet/react-native-image-zoom';
import { useFocusEffect } from '@react-navigation/native';
import { validateNumber } from '../../../constants/validations';
import { ConfirmSaveAlert } from '../../../components/Alert';
import { SafeAreaView } from 'react-native-safe-area-context';
import moment from 'moment';
import Loader from '../../../constants/Loader';
import { useSelector } from 'react-redux';
const FeedbackFormScreen = ({ route, navigation }) => {
const { CategoryId, SurveyId, CategoryName , storeData} = route.params || {};
let user_exist_data = useSelector(state => state?.user);
const userId = user_exist_data.UserId || '' ;
const [questions, setQuestions] = useState([]);
const [answers, setAnswers] = useState({});
const [enabledQuestions, setEnabledQuestions] = useState({});
const [showCamera, setShowCamera] = useState(false);
const [activeCameraQId, setActiveCameraQId] = useState(null);
const [selectedImg, setSelectedImg] = useState(null);
const [questionsFlat, setQuestionsFlat] = useState([]);
const [showModal, setShowModal] = useState(false);
const [zoomImageUrl, setZoomImageUrl] = useState(null);
const [showZoomImage, setShowZoomImage] = useState(false);
const [showAlert, setShowAlert] = useState(false);
const [loadingsave, setLoadingSave] = useState(false);
const d2 = moment().format('MM/DD/YYYY');
// handle disable and enable functions
console.log("storeDatastoreDatastoreData",storeData)
const disableQtn = () => {
let disableQ = questions?.map(q => {
});
}
// end handle disable and enable functions
const loadSavedAnswers = async () => {
const query = `SELECT * FROM FeedBackLocalTable WHERE CATEGORY_ID='${CategoryId}' AND SURVEY_ID='${SurveyId}' AND STORE_ID='${storeData?.StoreId}' AND VISIT_DATE='${d2}'`;
// console.log("local saved answers query ===>", query)
db.transaction(tx => {
tx.executeSql(
query,
[],
(tx, results) => {
const localAnswers = {};
let enb = {}
for (let i = 0; i < results.rows.length; i++) {
const item = results.rows.item(i);
let answer = null;
if (item.IMAGE1 && item.IMAGE1 !== '') {
answer = item.IMAGE1.trim();
} else if (item.ANSWER && item.ANSWER !== '') {
if (typeof item.ANSWER === 'string' && item.ANSWER.includes(',')) {
answer = item.ANSWER.split(',').map(val => parseInt(val.trim(), 10));
} else {
answer = item.ANSWER;
}
}
if (item.QUESTION_TYPE == 'Single choice list') {
answer = `${item?.ANSWER_ID},${item.ANSWER}`
} else if (item.QUESTION_TYPE == 'Multi choice list') {
answer = item?.MULTI_OPTIONS_IDS && JSON.parse(item?.MULTI_OPTIONS_IDS)
// console.log("answer------multi", answer, item)
}
enb = item?.IS_DISABLED && JSON.parse(item?.IS_DISABLED)
localAnswers[item.QUESTION_ID] = answer;
// console.log("item?.IMAGE1", item?.IMAGE1)
localAnswers[item.QUESTION_ID + "Img1"] = item?.IMAGE1 ? ('file:///data/user/0/com.performicsstoredna/files/PerformicsAllImages/Store_DNA/' + item.IMAGE1) : "";
localAnswers[item.QUESTION_ID + "Img2"] = item?.IMAGE1 ? ('file:///data/user/0/com.performicsstoredna/files/PerformicsAllImages/Store_DNA/' + item.IMAGE2) : "";
// enabledMap[item.QUESTION_ID] = item.IS_DISABLED === 1 ? false : true; // isDisabled
}
setAnswers(prev => ({
...prev,
...localAnswers
}));
// console.log("enb----",enb)
if (Object.keys(enb).length > 0) {
setEnabledQuestions(enb); // ✅ restore isDisabled state
}
},
(tx, error) => {
console.log('Error fetching saved answers:', error);
}
);
});
};
const saveData = async () => {
setLoadingSave(true);
try {
let valuesArray = [];
questions?.map(q => {
let answer = answers[q.QuestionId] || '';
let img1 = answers[q.QuestionId + "Img1"] || '';
let img2 = answers[q.QuestionId + "Img2"] || '';
let selanswer = "", selansid = "", multi = "";
// console.log("questionlist======", q, "questionlist====");
if (q.QuestionType == 'Single choice list') {
let slValue = answer?.split(",")
selanswer = slValue?.[1] || ''
selansid = slValue?.[0] || "0"
// console.log("selanswer==", selanswer, "selansid", selansid, "answ", answer, q)
} else if (q.QuestionType == 'Multi choice list') {
// console.log("multi------", answer)
multi = JSON.stringify(answer)
selansid = "0"
} else {
selanswer = answer
selansid = "0"
}
// console.log("selanswer==",selanswer ,"selansid" , selansid , "answ",answer)
let imgName1 = img1.split("/").pop();
let imgName2 = img2.split("/").pop();
// console.log("multi------multi",multi)
let enb = JSON.stringify(enabledQuestions)
valuesArray.push(` ('${d2}','${SurveyId}', '${CategoryId}','${q.Question}','${q.QuestionId}','${q.QuestionType}','${selanswer || ''}','${selansid}','${q.QuestionImageAllow}','${imgName1 || ''}','${imgName2 || ''}','${q.ImageAllow1}','${q.ImageAllow2}','${multi}','${q.QuestionRefImage}', '${enb}', '${q.EnableQuestion}' , '${storeData.StoreId || ''}') `);
});
const queryStr = valuesArray.join(', ');
let insertQuery = `INSERT INTO FeedBackLocalTable (VISIT_DATE, SURVEY_ID, CATEGORY_ID, QUESTION, QUESTION_ID, QUESTION_TYPE ,ANSWER,ANSWER_ID,QuestionImageAllow,IMAGE1,IMAGE2,IMAGE_ALLOW1,IMAGE_ALLOW2,MULTI_OPTIONS_IDS, QUESTION_REF_IMAGE, IS_DISABLED , IS_ENABLED, STORE_ID) VALUES ${queryStr}`
db.transaction(tx => {
let deleteQuery = `DELETE FROM FeedBackLocalTable WHERE CATEGORY_ID='${CategoryId}' AND SURVEY_ID='${SurveyId}' AND STORE_ID='${storeData?.StoreId}' AND VISIT_DATE='${d2}'`;
console.log('Executing DELETE query:', deleteQuery);
tx.executeSql(deleteQuery, [], () => {
// console.log("insertQuery------",insertQuery)
tx.executeSql(insertQuery, [], async function (txn2, txnres) {
toastSuccess('Success', 'Data saved successfully.');
setLoadingSave(false);
navigation.goBack();
}, function (txnE, txnerr) {
console.log('Delete error:', txnerr); toastError('Error', txnerr);
setLoadingSave(false);
},);
}, err => console.log('Delete error:', err));
});
} catch (e) {
toastError('Error', e);
setLoadingSave(false);
console.log('Transaction error:', e);
}
};
// const handleAnswerChange = (questionId, value, enableList = '', disableList = '', qtn) => {
// setAnswers(prev => ({ ...prev, [questionId]: value }));
// const updated = { ...enabledQuestions };
// if (disableList) {
// disableList.split(',').forEach(id => {
// updated[parseInt(id)] = false;
// });
// }
// if (enableList) {
// enableList.split(',').forEach(id => {
// updated[parseInt(id)] = true;
// });
// }
// setEnabledQuestions(updated);
// };
function cleanSelector(disableList,qtn,showImageTag,showImageTag2) {
let objTemp={...answers}
disableList?.split(",").map((itm,index)=>{
objTemp[itm]=""
delete objTemp[itm+"Img1"]
delete objTemp[itm+"Img2"]
})
if(showImageTag){
delete objTemp[qtn?.QuestionId+"Img1"]
}
if(showImageTag2){
delete objTemp[qtn?.QuestionId+"Img2"]
}
// console.log("objTemp-----3",showImageTag,showImageTag2,objTemp)s
setAnswers(objTemp);
}
const handleAnswerChange = (questionId, value, enableList = '', disableList = '', qtn,showImageTag=false,showImageTag2=false) => {
const prevValue = answers[questionId];
// Clean selector logic: only for Single choice list
if (
qtn.QuestionType === 'Single choice list' &&
prevValue &&
prevValue !== value
) {
cleanSelector(disableList,qtn,showImageTag,showImageTag2);
console.log('qtn------',qtn)
}
// Update the answer
setAnswers(prev => ({ ...prev, [questionId]: value }));
// Enable/disable dependent questions
const updated = { ...enabledQuestions };
if (disableList) {
disableList.split(',').forEach(id => {
updated[parseInt(id)] = false;
});
}
if (enableList) {
enableList.split(',').forEach(id => {
updated[parseInt(id)] = true;
});
}
setEnabledQuestions(updated);
};
const handleImageCaptured = async (photo) => {
console.log("activeCameraQId-----", activeCameraQId)
if (activeCameraQId && photo?.uri) {
try {
const imgdata = {
uri: photo.uri,
width: photo.width,
ExtendedHeight: photo.height,
storeData: {}
};
const res = await getImage(imgdata ,userId , storeData);
const imagePath = 'file://' + (res?.finalPath || photo.uri);
setAnswers(prev => ({
...prev,
[activeCameraQId]: imagePath
}));
setSelectedImg(imagePath);
setShowModal(true);
} catch (e) {
console.warn("Watermarking failed:", e);
}
}
setShowCamera(false);
};
const openCamera = (questionId, type = 0) => {
setActiveCameraQId(questionId);
setShowCamera(true);
};
// Fixed function to handle image tap - properly set activeCameraQId
const handleImageTap = (questionId, type = 0) => {
const currentImage = answers[questionId];
// Set the active camera question ID
setActiveCameraQId(questionId);
if (currentImage) {
setSelectedImg(currentImage);
setShowModal(true);
// setActiveCameraQId(questionId+"Img"+type);
} else {
openCamera(questionId, type);
}
};
const transformSurveyData = (flatData) => {
const grouped = {};
flatData?.forEach(item => {
if (!grouped[item.QuestionId]) {
grouped[item.QuestionId] = {
QuestionId: item.QuestionId,
Question: item.Question,
QuestionType: item.QuestionTypeNew,
QEnable: item.QEnable,
EnableQuestion: item.EnableQuestion,
DisableQuestion: item.DisableQuestion,
QuestionSequence: item.QuestionSequence,
CategorySequence: item.CategorySequence,
QuestionRefImage: item.QuestionRefImage,
MinLength: item.MinLength,
MaxLength: item.MaxLength,
LengthValidation: item.LengthValidation,
QuestionImageMandatory: item.QuestionImageMandatory, // images
QuestionImageAllow: item.QuestionImageAllow,
ImageAllow1: item.ImageAllow1,
ImageAllow2: item.ImageAllow2,
Image1Mandatory: item.Image1Mandatory,
Image2Mandatory: item.Image2Mandatory,
Answers: []
};
}
if (!['Number', 'Text', 'OnlyText'].includes(item.QuestionTypeNew)) {
if (item.AnswerId || item.Answer) {
grouped[item.QuestionId].Answers.push({
AnswerId: item.AnswerId,
Answer: item.Answer,
isDisabled: item.DisableQuestion,
isEnabled: item.EnableQuestion,
ImageAllow2: item.ImageAllow2,
ImageAllow1: item.ImageAllow1
});
}
}
});
return Object.values(grouped).sort((a, b) => a.QuestionSequence - b.QuestionSequence);
};
const loadQuestions = () => {
const query = `SELECT * FROM FeedbackCategory WHERE CategoryId = '${CategoryId}' AND SurveyId = '${SurveyId}' ORDER BY QuestionSequence ASC`;
console.log('Executing SELECT query:', query);
db.transaction(tx => {
tx.executeSql(
`SELECT * FROM FeedbackCategory WHERE CategoryId = ? AND SurveyId = ? ORDER BY QuestionSequence ASC`,
[CategoryId, SurveyId],
(tx, results) => {
const rows = [];
for (let i = 0; i < results.rows.length; i++) {
rows.push(results.rows.item(i));
}
// console.log('Fetched Questions:', rows);
setQuestionsFlat(rows);
const transformed = transformSurveyData(rows);
setQuestions(transformed);
const enableMap = {};
transformed.forEach(q => {
enableMap[q.QuestionId] = q.QEnable === true || q.QEnable === 1;
});
setEnabledQuestions(enableMap);
loadSavedAnswers();
},
(tx, err) => {
console.log('Error loading questions:', err);
}
);
});
};
const openRefImageView = (imgUrl) => {
setZoomImageUrl(imgUrl);
setShowZoomImage(true);
};
useEffect(() => {
loadQuestions();
}, []);
useFocusEffect(
React.useCallback(() => {
const onBackPress = () => {
if (showZoomImage) {
setShowZoomImage(false);
return true; // block default back action
}
return false; // allow default back behavior
};
const subscription = BackHandler.addEventListener('hardwareBackPress', onBackPress);
return () => subscription.remove(); // ✅ modern cleanup
}, [showZoomImage])
);
async function validate() {
let isValid = true;
for (let qtn of questions) {
// Skip disabled questions
// console.log("enabledQuestions[qtn.QuestionId]", typeof enabledQuestions[qtn.QuestionId] , qtn)
if (!enabledQuestions[qtn.QuestionId]) continue;
let selansid = '', selanswer = '', selimg = '', showImageTag = false, showImageTag2 = false, isImageMandateforAns = false;
// console.log("answers===", JSON.stringify(answers))
const answer = answers[qtn.QuestionId];
const qType = qtn.QuestionType;
const qText = qtn.Question;
let image1 = answers[qtn?.QuestionId + "Img1"] || '';
let image2 = answers[qtn.QuestionId + "Img2"] || '';
// console.log(answers, "answer==========", image1, answer)
// images
const isTruthy = (val) => val === true || val === 'true' || val === 1 || val === '1';
let isImg1mandatory = isTruthy(qtn.Image1Mandatory) || isTruthy(qtn.QuestionImageMandatory);
let isImg2mandatory = isTruthy(qtn.Image2Mandatory);
let isImgAllowed = ((qtn.QuestionImageAllow == true || qtn.QuestionImageAllow == 'true' || qtn.QuestionImageAllow == 1) || (qtn.QuestionType == "Image"));
// let isImg1mandatory = (qtn.Image1Mandatory == "true") || (qtn.Image1Mandatory == true) || (qtn.QuestionImageMandatory == "true") || (qtn.QuestionImageMandatory == true)
// let isImg2mandatory = (qtn?.Image2Mandatory == "true" || qtn?.Image2Mandatory == 'true');
let isQuestionImageMandatory = qtn.QuestionType == "Image" ? (qtn?.QuestionImageMandatory == "true" || qtn?.QuestionImageMandatory == true) : true;
// console.log("isImg1mandatory qutn", qtn.Image1Mandatory, "questionss", qtn)
// TEXT / NUMBER / ONLYTEXT fields
if (['Text', 'Number', 'OnlyText'].includes(qType)) {
if (!answer || answer === '') {
toastError('Please fill details for: ', `${qText}`);
isValid = false;
break;
}
const minL = qtn.MinLength || 0;
const maxL = qtn.MaxLength || 10;
const needsLengthValidation = qtn.LengthValidation === 'true' || qtn.LengthValidation === true || qtn.LengthValidation === 1; // works file recheck
// console.log("needsLengthValidation",needsLengthValidation)
if (needsLengthValidation) {
if (answer?.length < minL) {
toastError('', `Minimum ${minL} characters required in: ${qText}`);
isValid = false;
break;
}
if (answer?.length > maxL) {
toastError('', `Maximum ${maxL} characters allowed in: ${qText}`);
isValid = false;
break;
}
}
}
// SINGLE CHOICE
else if (qType === 'Single choice list') {
console.log("Single choice questions", qType, answer)
const selectedValue = answer?.split(',')[1]; // get the actual ANSWER value
if (!selectedValue || selectedValue.trim() === '') {
toastError('Please select an option for:', `${qText}`);
isValid = false;
break;
}
// if (!answer || answer === '') {
// toastError('Please select an option for:', `${qText}`);
// isValid = false;
// break;
// }
}
// MULTI CHOICE
else if (qType === 'Multi choice list') {
if (!Array.isArray(answer) || answer.length === 0) {
toastError('Please select at least one option for:', `${qText}`);
isValid = false;
break;
}
}
// IMAGE
else if (qType === 'Image') {
if (!image1 || image1 === '') {
toastError('', `Please capture image for: ${qText}`);
isValid = false;
break;
}
}
if ((qtn.QuestionType == 'Single choice list' || qtn.QuestionType == 'Multi choice list')) {
// console.log("answers[qtn?.QuestionId]----", answers[qtn?.QuestionId], qtn?.QuestionId)
if (qtn.QuestionType != 'Multi choice list') {
let slValue = answers[qtn?.QuestionId]?.split(",")
selanswer = slValue?.[1] || ''
selansid = slValue?.[0] || '0'
}
if (qtn.Answers && qtn.Answers.length > 0) {
for (var i = 0; i < qtn.Answers.length; i++) {
let ansss = qtn.Answers[i];
let isandImgAl = (ansss.ImageAllow1 == true || ansss.ImageAllow1 == 'true' || ansss.ImageAllow1 == 1);
let isandImgAl2 = (ansss.ImageAllow2 == true || ansss.ImageAllow2 == 'true' || ansss.ImageAllow2 == 1);
let an = answers[qtn.QuestionId]
// console.log("showImageTag2------", isandImgAl, ansss, qtn.AnswerId, qtn, an, answers)
if (qtn.QuestionType == 'Single choice list' && (isandImgAl && ansss.AnswerId == selansid)) {
showImageTag = true;
}
else if (qtn.QuestionType == 'Multi choice list' && (isandImgAl && qtn?.AnswerId?.indexOf(ansss?.AnswerId) >= 0)) {
showImageTag = true;
}
if (qtn.QuestionType == 'Single choice list' && (isandImgAl2 && ansss.AnswerId == selansid)) {
showImageTag2 = true;
}
else if (qtn.QuestionType == 'Multi choice list' && (isandImgAl2 && qtn?.AnswerId?.indexOf(ansss?.AnswerId) >= 0)) {
showImageTag2 = true;
}
}
}
}
// console.log("isImg1mandatory answerrrrrrrr", isImg1mandatory, image1, image2)
if (showImageTag && isImg1mandatory && (!image1 || image1 === '')) {
isValid = false;
toastError('', `Please capture image for: ${qText}`);
break;
}
if (showImageTag2 && isImg2mandatory && (!image2 || image2 === '')) {
isValid = false;
toastError('', `Please capture image for 2: ${qText}`);
break;
}
// Other types like Audio, Rating, Date can be added similarly here
}
return isValid;
}
function setTextValue(value, qtn, type = '') {
if (value !== '') {
const isValid = validateNumber(value, type);
if (!isValid) return;
}
let valToSave = value;
// If it's decimal type and requires rounding
if (qtn.QuestionType === 'Decimal' && qtn.LengthValidation === 'true') {
const valSplit = value.split('.')[1]?.length;
if (valSplit === undefined || valSplit < 2) {
valToSave = value;
} else if (/^\d+\.\d{2}$/.test(value)) {
valToSave = Number(value).toFixed(2);
}
}
setAnswers(prev => ({
...prev,
[qtn.QuestionId]: valToSave
}));
}
async function onSubmitData() {
let isvalid = await validate();
if (isvalid) {
Keyboard.dismiss();
setShowAlert(true);
}
}
function onSaveCancel() {
setShowAlert(false);
}
return (
<SafeAreaView style={{ flex: 1, backgroundColor: GlobalTheme.colors.primary }}>
<CustomHeader title="Feedback and Rating" rightIcon={IMAGES.menuIcon} leftIcon={IMAGES.leftArrowIcon} onLeftPress={() => navigation.goBack()} onRightPress={() => navigation.navigate('Dashboard')} />
<KeyboardAwareScrollView extraScrollHeight={Platform.OS === 'android' ? 200 : 0} keyboardShouldPersistTaps="handled" enableOnAndroid contentContainerStyle={{ flexGrow: 1 }} style={{ flex: 1, backgroundColor: GlobalTheme.colors.white }}>
<View style={{ flex: 1, backgroundColor: GlobalTheme.colors.white }}>
<View style={styles.container}>
<View style={styles.cardview}>
<Text style={styles.categoryName}>{CategoryName}</Text>
{questions.map((qtn) => {
let selansid = '', selanswer = '';
let imgQS = qtn.QuestionId + "Img"
if (!enabledQuestions[qtn.QuestionId]) return null;
let maxlen = (qtn.LengthValidation == true || qtn.LengthValidation == 'true') && qtn.MaxLength != null && qtn.MaxLength != '' ? qtn.MaxLength : 10;
let minlen = (qtn.LengthValidation == true || qtn.LengthValidation == 'true') && qtn.MinLength != null && qtn.MinLength != '' ? qtn.MinLength : 0;
// question image
let isImgAllowed = ((qtn.QuestionImageAllow == true || qtn.QuestionImageAllow == 'true' || qtn.QuestionImageAllow == 1) || (qtn.QuestionType == "Image"));
let isQuestionImageMandatory = qtn.QuestionType == "Image" ? (qtn?.QuestionImageMandatory == "true" || qtn?.QuestionImageMandatory == true) : true;
// Answer Images
let Image1Mandatory = (qtn.Image1Mandatory == "true") || (qtn.Image1Mandatory == true) || (qtn.QuestionImageMandatory == "true") || (qtn.QuestionImageMandatory == true)
let Image2Mandatory = (qtn.Image2Mandatory == "true") || (qtn.Image2Mandatory == true)
{/* console.log(Image1Mandatory, "Image1Mandatory", qtn.QuestionImageMandatory, "qtn.QuestionImageMandatory") */ }
{/* console.log("maxlen", maxlen, "===========", "minlen", minlen); */ }
let showImageTag = false, showImageTag2 = false
if (qtn.QuestionType == 'Image' && isImgAllowed) {
showImageTag = true;
}
if ((qtn.QuestionType == 'Single choice list' || qtn.QuestionType == 'Multi choice list')) {
{/* console.log("answers[qtn?.QuestionId]----", answers[qtn?.QuestionId], qtn?.QuestionId) */ }
if (qtn.QuestionType != 'Multi choice list') {
let slValue = answers[qtn?.QuestionId]?.split(",")
selanswer = slValue?.[1] || ''
selansid = slValue?.[0] || '0'
}
if (qtn.Answers && qtn.Answers.length > 0) {
for (var i = 0; i < qtn.Answers.length; i++) {
let ansss = qtn.Answers[i];
let isandImgAl = (ansss.ImageAllow1 == true || ansss.ImageAllow1 == 'true' || ansss.ImageAllow1 == 1);
let isandImgAl2 = (ansss.ImageAllow2 == true || ansss.ImageAllow2 == 'true' || ansss.ImageAllow2 == 1);
let an = answers[qtn.QuestionId]
{/* console.log("showImageTag2------", isandImgAl, ansss, qtn.AnswerId, qtn, an, answers) */ }
if (qtn.QuestionType == 'Single choice list' && (isandImgAl && ansss.AnswerId == selansid)) {
showImageTag = true;
}
else if (qtn.QuestionType == 'Multi choice list' && (isandImgAl && qtn?.AnswerId?.indexOf(ansss?.AnswerId) >= 0)) {
showImageTag = true;
}
if (qtn.QuestionType == 'Single choice list' && (isandImgAl2 && ansss.AnswerId == selansid)) {
showImageTag2 = true;
}
else if (qtn.QuestionType == 'Multi choice list' && (isandImgAl2 && qtn?.AnswerId?.indexOf(ansss?.AnswerId) >= 0)) {
showImageTag2 = true;
}
}
}
}
return (
<View key={qtn.QuestionId} style={styles.questionBlock}>
<Text style={styles.questionText}>{qtn.Question}</Text>
{/* {console.log("qtn.QuestionRefImage", qtn.QuestionRefImage, Image2Mandatory)} */}
{qtn.QuestionRefImage != null && qtn.QuestionRefImage != '' && (
<TouchableOpacity style={styles.WItem_RefImgBtn} onPress={() => openRefImageView(qtn.QuestionRefImage)}>
<CustomIcon iconLibrary={'Entypo'} icon='image-inverted' size={30} color="black" />
</TouchableOpacity>
)}
{qtn.QuestionType === 'Single choice list' && (
<ModalSelector animationType='fade' optionContainerStyle={{ backgroundColor: '#fff' }} cancelContainerStyle={{ backgroundColor: '#fff' }}
key={qtn.QuestionId}
data={qtn.Answers.map(ans => ({ key: ans.AnswerId, label: ans.Answer }))}
initValue="Select one"
onChange={option => {
// console.log("option---", option)
const selectedAnswer = option.label;
const matchedRow = questionsFlat?.find(item =>
item.QuestionId === qtn.QuestionId && item.Answer === selectedAnswer
);
const enableQ = matchedRow?.EnableQuestion || '';
const disableQ = matchedRow?.DisableQuestion || '';
console.log("showImageTag------",showImageTag,showImageTag2)
handleAnswerChange(
qtn.QuestionId,
`${option?.key},${selectedAnswer}`,
enableQ,
disableQ,
qtn,
showImageTag,
showImageTag2
);
}}
>
<Text style={styles.selectorBox}>
{selanswer || '---Select option---'}
</Text>
</ModalSelector>
)}
{qtn.QuestionType === 'Multi choice list' && (
<MultiSelect
// items={list_mcdata}
// uniqueKey="AnswerId"
// onSelectedItemsChange={(selectedItems) => { console.log('onSelectedItemsChange'); setMultiSelectValue(selectedItems, qtn) }}
// selectedItems={selansid}
items={qtn.Answers}
uniqueKey="AnswerId"
displayKey="Answer"
onSelectedItemsChange={(selected) =>
handleAnswerChange(qtn.QuestionId, selected, qtn.EnableQuestion, qtn.DisableQuestion, qtn , "", "")
}
selectedItems={
Array.isArray(answers[qtn.QuestionId])
? answers[qtn.QuestionId].map(a => parseInt(a))
: []
}
selectText="Pick Items"
searchInputPlaceholderText="Search Items..."
searchIcon={<Icon name="search" size={18} color="#888" />}
tagRemoveIconColor="#ccc"
tagBorderColor="#ccc"
tagTextColor="#000"
selectedItemTextColor={GlobalTheme.colors.primary}
selectedItemIconColor="#000"
itemTextColor="#000"
styleMainWrapper={styles.multiSelect}
submitButtonText={'Submit'}
textColor={GlobalTheme.colors.black}
submitButtonColor={GlobalTheme.colors.secondary}
styleItemsContainer={styles.styleItemsContainer}
styleListContainer={styles.styleListContainer}
styleInputGroup={styles.styleInputGroup}
styleDropdownMenuSubsection={styles.styleDropdownMenuSubsection}
styleDropdownMenu={styles.styleDropdownMenu}
/>
)}
{(qtn.QuestionType === 'Text' || qtn.QuestionType === 'OnlyText' || qtn.QuestionType === 'Number') && (
<TextInput
style={styles.textInput}
placeholder="Enter here..."
placeholderTextColor={'gray'}
keyboardType={qtn.QuestionType === 'Number' ? 'number-pad' : 'default'}
value={answers[qtn.QuestionId] || ''}
minLength={minlen}
maxLength={maxlen}
onChangeText={(val) => { setTextValue(val, qtn, qtn.QuestionType == 'Decimal' ? 'decimal' : qtn.QuestionType == 'Text' ? 'text' : qtn.QuestionType == 'OnlyText' ? 'onlytext' : 'numeric') }}
// onChangeText={(text) => handleAnswerChange(qtn.QuestionId, text)}
returnKeyType={(Platform.OS === 'ios') ? 'done' : 'next'}
/>
)}
<View style={{ flexDirection: 'row', alignItems: 'center', flex: 1 }}>
{showImageTag &&
<View style={[]}>
<TouchableOpacity onPress={() => { handleImageTap(imgQS + 1) }}>
<Image source={answers[imgQS + 1] ? IMAGES.greenCameraIcon : Image1Mandatory ? IMAGES.redCameraIcon : IMAGES.normalCameraIcon} style={styles.iconStyle} />
</TouchableOpacity>
</View>
}
{showImageTag2 &&
<View style={[]}>
<TouchableOpacity onPress={() => { handleImageTap(imgQS + 2) }}>
<Image source={answers[imgQS + 2] ? IMAGES.greenCameraIcon : Image2Mandatory ? IMAGES.redCameraIcon : IMAGES.normalCameraIcon} style={styles.iconStyle} />
</TouchableOpacity>
</View>
}
</View>
</View>
);
})}
</View>
</View>
{showZoomImage && zoomImageUrl && (
<View style={styles.album_ZOOM_wrap}>
<TouchableOpacity style={styles.closeIcon} onPress={() => setShowZoomImage(false)} >
<Icon name="times-circle" size={35} color="#fff" />
</TouchableOpacity>
<ImageZoom cropWidth={Screen.screenWidth} cropHeight={Screen.screenHeight} imageWidth={Screen.screenWidth} imageHeight={Screen.screenHeight} style={styles.album_ZOOM} uri={zoomImageUrl} minScale={1} maxScale={5} doubleTapScale={3} isSingleTapEnabled isDoubleTapEnabled resizeMode="contain" />
</View>
)}
{showModal && selectedImg && (
<Modal visible={showModal} transparent={true} animationType="fade" onRequestClose={() => setShowModal(false)}>
<View style={{ flex: 1, backgroundColor: '#00000088', justifyContent: 'center', alignItems: 'center' }}>
<View style={styles.modal_view}>
<Image source={{ uri: selectedImg }} style={{ width: 250, height: 250, borderRadius: 10, marginBottom: 15 }} resizeMode="contain" />
<TouchableOpacity
style={styles.modal_view2}
onPress={() => {
setShowModal(false);
if (activeCameraQId) {
openCamera(activeCameraQId);
}
}}
>
<Text style={{ color: '#fff' }}>Retake</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.modal_view2} onPress={() => setShowModal(false)}>
<Text style={{ color: '#fff' }}>Close</Text>
</TouchableOpacity>
</View>
</View>
</Modal>
)}
<ConfirmSaveAlert showAlert={showAlert} onCancelCallBack={onSaveCancel} onYesCallBack={saveData} msg="Do you really want to save data?" />
{showCamera && (
<Modal visible={showCamera} animationType="slide" presentationStyle="fullScreen">
<CustomCamera
onImageCaptured={handleImageCaptured}
onClose={() => setShowCamera(false)}
/>
</Modal>
)}
<Loader visible={loadingsave} loadingtext={'Saving Data...'} />
</View>
</KeyboardAwareScrollView>
<View style={{ backgroundColor: GlobalTheme.colors.white }}>
<CustomButton onPress={onSubmitData} title={'Save'} style={styles.btnbg} textstyle={styles.btntext} />
</View>
</SafeAreaView>
);
};
export default FeedbackFormScreen;
@@ -0,0 +1,232 @@
{
"mappingsurvey":[
{
"StoreId": 403,
"SurveyId": 1
},
{
"StoreId": 484,
"SurveyId": 1
},
{
"StoreId": 510,
"SurveyId": 1
},
{
"StoreId": 583,
"SurveyId": 1
},
{
"StoreId": 2928,
"SurveyId": 1
},
{
"StoreId": 2949,
"SurveyId": 1
},
{
"StoreId": 1,
"SurveyId": 1
},
{
"StoreId": 2,
"SurveyId": 1
},
{
"StoreId": 3,
"SurveyId": 1
},
{
"StoreId": 4,
"SurveyId": 1
},
{
"StoreId": 5,
"SurveyId": 1
},
{
"StoreId": 1,
"SurveyId": 9
},
{
"StoreId": 2,
"SurveyId": 9
},
{
"StoreId": 3,
"SurveyId": 9
},
{
"StoreId": 4,
"SurveyId": 9
},
{
"StoreId": 5,
"SurveyId": 9
},
{
"StoreId": 585,
"SurveyId": 9
},
{
"StoreId": 2532,
"SurveyId": 9
},
{
"StoreId": 1,
"SurveyId": 10
},
{
"StoreId": 2,
"SurveyId": 10
},
{
"StoreId": 3,
"SurveyId": 10
},
{
"StoreId": 4,
"SurveyId": 10
},
{
"StoreId": 1171,
"SurveyId": 11
},
{
"StoreId": 1,
"SurveyId": 12
},
{
"StoreId": 2,
"SurveyId": 12
},
{
"StoreId": 3,
"SurveyId": 12
},
{
"StoreId": 4,
"SurveyId": 12
},
{
"StoreId": 5,
"SurveyId": 12
},
{
"StoreId": 691,
"SurveyId": 10
},
{
"StoreId": 2949,
"SurveyId": 10
},
{
"StoreId": 402,
"SurveyId": 10
},
{
"StoreId": 484,
"SurveyId": 10
},
{
"StoreId": 2928,
"SurveyId": 10
},
{
"StoreId": 403,
"SurveyId": 10
},
{
"StoreId": 583,
"SurveyId": 10
},
{
"StoreId": 3739,
"SurveyId": 10
},
{
"StoreId": 510,
"SurveyId": 10
},
{
"StoreId": 2958,
"SurveyId": 10
},
{
"StoreId": 4041,
"SurveyId": 10
},
{
"StoreId": 2920,
"SurveyId": 10
},
{
"StoreId": 45,
"SurveyId": 11
},
{
"StoreId": 402,
"SurveyId": 11
},
{
"StoreId": 484,
"SurveyId": 11
},
{
"StoreId": 510,
"SurveyId": 11
},
{
"StoreId": 653,
"SurveyId": 11
},
{
"StoreId": 1122,
"SurveyId": 11
},
{
"StoreId": 1483,
"SurveyId": 11
},
{
"StoreId": 1532,
"SurveyId": 11
},
{
"StoreId": 1605,
"SurveyId": 11
},
{
"StoreId": 1711,
"SurveyId": 11
},
{
"StoreId": 2091,
"SurveyId": 11
},
{
"StoreId": 2308,
"SurveyId": 11
},
{
"StoreId": 2532,
"SurveyId": 11
},
{
"StoreId": 2928,
"SurveyId": 11
},
{
"StoreId": 2949,
"SurveyId": 11
},
{
"StoreId": 2958,
"SurveyId": 11
},
{
"StoreId": 3198,
"SurveyId": 11
}
]
}
+103
View File
@@ -0,0 +1,103 @@
import { StyleSheet } from 'react-native';
import { GlobalTheme, Screen } from '../../../theme';
export const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: GlobalTheme.colors.white,
paddingHorizontal: 10,
paddingVertical: 10
},
btnbg: {
backgroundColor: GlobalTheme.colors.secondary, borderRadius: GlobalTheme.borderRadius.md , marginVertical:10 , marginHorizontal:10 , bottom:0
},
btntext: {
color: GlobalTheme.colors.white,
fontSize: GlobalTheme.typography.fontSize.medium
},
cardview: {
borderColor: GlobalTheme.colors.lightblue, borderWidth: 1, borderRadius: GlobalTheme.borderRadius.lg, padding: 10
},
questionBlock: {
marginBottom: 16,
},
questionText: {
color:'#000',
fontSize: GlobalTheme.typography.fontSize.xsmall,
marginVertical: 5
},
categoryName: {
fontSize: GlobalTheme.typography.fontSize.medium , fontWeight : GlobalTheme.typography.fontWeight.medium , color : GlobalTheme.colors.black , marginVertical:0
},
textInput: {
borderWidth: 1.5,
borderColor: GlobalTheme.colors.lightblue,
borderRadius: 6,
padding: 10,
color : GlobalTheme.colors.black,
marginVertical: 5
},
multiSelect: {
// marginTop: 8,
// backgroundColor:'red'
},
selectorBox: {
color:'gray',
padding: 15,
backgroundColor: GlobalTheme.colors.bluebgcolor,
borderRadius: GlobalTheme.borderRadius.sm,
marginVertical: 5
},
//multi select
styleItemsContainer:{
backgroundColor: GlobalTheme.colors.bluebgcolor , borderRadius :GlobalTheme.borderRadius.md , overflow:'hidden', paddingHorizontal:10, marginHorizontal:0,marginVertical:0 , marginBottom:20
},
styleListContainer:{
backgroundColor: GlobalTheme.colors.bluebgcolor , borderRadius :GlobalTheme.borderRadius.md , padding:0 , overflow:'hidden', paddingHorizontal:0, marginHorizontal:0,marginVertical:10
},
styleInputGroup:{
backgroundColor: GlobalTheme.colors.bluebgcolor , borderRadius :GlobalTheme.borderRadius.md , padding:0 , overflow:'hidden',paddingHorizontal:10, marginVertical:10,height:50
},
styleDropdownMenuSubsection:{
backgroundColor: GlobalTheme.colors.bluebgcolor , borderRadius :GlobalTheme.borderRadius.md , padding:0 , overflow:'hidden',paddingHorizontal:10, marginHorizontal:10
},
styleDropdownMenu:{
backgroundColor: GlobalTheme.colors.bluebgcolor , borderRadius :GlobalTheme.borderRadius.md , overflow:'hidden', marginVertical:10, height:50
},
// camera
openStk_prdCameras:{marginTop:10,flexDirection:'row',flexWrap:'wrap'},
openStkCameraCon:{marginRight:10,},
openStkCameraCon2:{marginRight:10,alignItems: 'center'},
iconStyle : {
height :70 , width : 70 , resizeMode : "contain"
},
// refImage
WItem_RefImgBtn:{position:'absolute',right:0,top:-5,width:40,height:40,borderRadius:20,alignItems:'center',justifyContent:'center'},
album_ZOOM_wrap:{backgroundColor:'#000',position:'absolute',top:0,left:0,width:Screen.screenWidth,height:Screen.screenHeight,zIndex:1},
album_ZOOM:{backgroundColor:'#000',width:Screen.screenWidth,height:Screen.screenHeight},
closeIcon : {
resizeMode : 'contain',
alignSelf:'flex-end',
right:20,
marginTop:20
},
store_audit_img_con:{width:'100%',marginTop:10,height:150,marginHorizontal:5,alignSelf:'center',alignItems:'center',justifyContent:'center',position:'relative',backgroundColor:'#f5f5f5'},
modal_view : {
backgroundColor: 'white',
borderRadius: 12,
padding: 20,
width: 300,
alignItems: 'center',
elevation: 10
},
modal_view2: {
marginBottom: 15,
paddingHorizontal: 25,
paddingVertical: 10,
backgroundColor: GlobalTheme.colors.secondary,
borderRadius: 6
}
});
@@ -0,0 +1,437 @@
import React, { useState, useRef, useEffect } from 'react';
import { View, Text, TouchableOpacity, ScrollView, FlatList } from 'react-native';
import { styles } from './style';
import { horizonalLine, Screen } from '../../../theme/theme';
import CustomHeader from '../../../components/CustomHeader';
import IMAGES from '../../../constants/Images';
import axios from 'axios';
import Loader from '../../../constants/Loader';
const storeinfodata = [
{
tabId: 0,
section: 'Store details',
items: [
{ brand: 'Store Name', name: 'Reliance Smart' },
{ brand: 'Store ID', name: '#98440' },
{ brand: 'Address', name: 'Rabindra Nagar, Delhi 110003' },
{ brand: 'City', name: 'Okhla' },
{ brand: 'Store Size', name: '15002000 sq ft' },
{ brand: 'Average Footfall', name: '10000' },
{ brand: 'Store age', name: '8' },
{ brand: 'Store Ranking', name: '24' },
],
},
{
tabId: 0,
section: 'Dabur Employees',
items: [
{ brand: 'RKAM', name: 'Rajesh Paal Singh' },
{ brand: 'SO', name: 'Soniya Singhal' },
],
},
{
tabId: 0,
section: '3P employees',
items: [
{ brand: 'Promoter Name', name: 'Payal Singh' },
{ brand: 'AM Name', name: 'Soniya Singhal' },
{ brand: 'Supervisor Name', name: 'Soniya Dhankar' },
{ brand: 'City', name: 'Okhla' },
{ brand: 'Promoter duration', name: '2 Year 7 months' },
{ brand: 'Average incentive', name: '5000' },
],
},
// Last Visit Details (tabId: 1)
{
tabId: 1,
section: 'Dabur employee',
items: [
{ brand: 'SO', name: 'Rajesh Paal Singh', date: '23/05/2025', test: 'gxsjhxbas' },
{ brand: 'AH', name: 'Umesh Singh', date: '18/04/2025' },
{ brand: 'KAM', name: 'Rahul Tawde', date: '15/05/2025' },
{ brand: 'Others', name: 'Singhal Singh', date: '06/05/2025' },
],
},
{
tabId: 1,
section: '3P team',
items: [
{ brand: 'Supervisor', name: 'Ashish Talwar', date: '27/05/2025' },
{ brand: 'AM', name: 'Soniya Singhal', date: '23/05/2025' },
],
},
// Competition (tabId: 2)
{
tabId: 2,
section: 'Competition assets',
items: [
{ brand: 'Marico', value: 5 },
{ brand: 'Colgate', value: 1 },
{ brand: 'Godrej', value: 2 },
{ brand: 'HUL', value: 1 },
{ brand: 'XX', value: 2 },
],
},
{
tabId: 2,
section: 'Promoter details',
items: [
{ brand: 'Marico', value: 1 },
{ brand: 'Colgate', value: 1 },
{ brand: 'Godrej', value: 1 },
{ brand: 'HUL', value: 1 },
{ brand: 'XX', value: 0 },
],
},
];
const tabs = [
{ id: 0, title: 'Store Info' },
{ id: 1, title: 'Last visit details' },
{ id: 2, title: 'Competition' },
];
const RenderHeader = ({ columns }) => {
const colWidth = Screen.screenWidth * 0.95 / columns?.length;
return (
null
// <View style={{ flexDirection: 'row', paddingVertical: 6 }}>
// {columns.map((col, index) => (
// <View key={index} style={{ width: colWidth }}>
// <Text style={styles.subheaderText}>{col.toUpperCase()}</Text>
// </View>
// ))}
// </View>
);
};
const RenderItem = ({ item, columns }) => {
const colWidth = Screen.screenWidth * 0.95 / columns?.length;
console.log('item------>',item);
let col=item?.brand == 'Designation' ? [...columns[0]]:columns
return (
<View>
<View style={styles.row}>
{col.map((key, index) => (
<View key={index} style={{ width: colWidth }}>
{console.log("columns---",columns,"Designation",item)}
<Text style={styles.name}>{item?.brand == 'Designation'? item?.name :String(item[key] ?? '')}</Text>
</View>
))}
</View>
<View style={[horizonalLine, { marginVertical: 4 }]} />
</View>
);
};
const SectionListView = ({ listData }) => {
const scrollRef = useRef(null); // shared horizontal scroll ref
console.log("/-",listData)
return (
<FlatList
data={listData}
keyExtractor={(item, index) => item.section + index}
contentContainerStyle={{ paddingBottom: 20 }}
ItemSeparatorComponent={() => <View style={{ paddingVertical: 5 }} />}
renderItem={({ item }) => {
const columns = Object.keys(item.items[0] || {}).filter(
key => item.items.some(obj => obj[key] !== undefined && obj[key] !== null)
);
return (
<View style={styles.section}>
<Text style={styles.sectionTitle}>{item.section}</Text>
<View style={[horizonalLine, { marginVertical: 10 }]} />
{/* Shared horizontal scroll view for header + rows */}
<ScrollView
horizontal
ref={scrollRef}
showsHorizontalScrollIndicator={false}
scrollEventThrottle={16}
>
<View>
<RenderHeader columns={columns} />
{item.items.map((subItem, index) => (
<RenderItem key={index} item={subItem} columns={columns} />
))}
</View>
</ScrollView>
</View>
);
}}
/>
);
};
const StoreInfo = ({ navigation, route }) => {
const { storeData } = route.params || {};
const [loading, setLoading] = useState(false)
const [selectedTab, setSelectedTab] = useState(0);
const [storeInfoData, setStoreInfoData] = useState([])
useEffect(() => {
getStoreInfo()
}, [])
// const getStoreInfo = async () => {
// setLoading(true)
// try {
// const params = { StoreId: storeData?.StoreId || "723" };
// const config = {
// method: 'post',
// url: 'https://api1.parinaam.in/api/dabur/StoreDNAstoreInfo',
// headers: {
// 'api_key': '9a1f056fecb84eaf8eb4152dda22ab0501955c4f9bbe7daa8780740459fdde7a',
// 'Content-Type': 'application/json'
// },
// data: params
// };
// const response = await axios.request(config);
// const res = response.data?.StoreDNAstoreInfo || {};
// // Flatten & transform API response into format usable by SectionListView
// const tempData = [];
// Object.entries(res).forEach(([sectionTitle, dataArray]) => {
// if (dataArray?.length > 0) {
// const tabId = dataArray[0]?.TabId ?? 0;
// // Convert single object to list of { key: value } pairs
// dataArray.forEach(item => {
// const cleanItem = { ...item };
// delete cleanItem.TabId;
// delete cleanItem.TabName;
// const formattedItems = Object.entries(cleanItem).map(([key, value]) => ({
// brand: key,
// name: String(value)
// }));
// tempData.push({
// tabId,
// section: sectionTitle.replace(/_/g, ' ').replace(/\b\w/g, c => c.toUpperCase()),
// items: formattedItems
// });
// });
// }
// });
// setStoreInfoData(tempData);
// setLoading(false)
// } catch (error) {
// console.log("❌ store info api error:", error);
// setLoading(false)
// }
// };
const getStoreInfo = async () => {
setLoading(true);
try {
const params = { StoreId: storeData?.StoreId || "723" };
const config = {
method: 'post',
url: 'https://api1.parinaam.in/api/dabur/StoreDNAstoreInfo',
headers: {
'api_key': '9a1f056fecb84eaf8eb4152dda22ab0501955c4f9bbe7daa8780740459fdde7a',
'Content-Type': 'application/json'
},
data: params
};
const response = await axios.request(config);
const res = response.data?.StoreDNAstoreInfo || {};
// Step 1: Flatten API data
const tempData = [];
Object.entries(res).forEach(([sectionTitle, dataArray]) => {
if (dataArray?.length > 0) {
const tabId = dataArray[0]?.TabId ?? 0;
let sec =sectionTitle.replace(/_/g, ' ').replace(/\b\w/g, c => c.toUpperCase())
dataArray.forEach(item => {
const cleanItem = { ...item };
delete cleanItem.TabId;
delete cleanItem.TabName;
let formattedItems=[]
if("Dabur Employee"==sec){
console.log("tempData---14",item,sec)
formattedItems = Object.entries(cleanItem).map(([key, value]) => ({
brand: key,
name: String(value)
}));
}else{
formattedItems = Object.entries(cleanItem).map(([key, value]) => ({
brand: key,
name: String(value)
}));
}
tempData.push({
tabId,
section: sec,
items: formattedItems
});
});
}
});
// Step 2: Group items by tabId and section
const groupedData = {};
tempData.forEach(entry => {
const key = `${entry.tabId}-${entry.section}`;
if (!groupedData[key]) {
groupedData[key] = {
tabId: entry.tabId,
section: entry.section,
items: []
};
}
groupedData[key].items.push(...entry.items);
});
// let objTemp={}
// groupedData['0-Dabur Employee']?.items
console.log("tempData-----gD",groupedData)
// Step 3: Set to state
setStoreInfoData(Object.values(groupedData));
setLoading(false);
} catch (error) {
console.log("❌ store info api error:", error);
setLoading(false);
}
};
const filteredData = Array.isArray(storeInfoData)
? storeInfoData.filter(item => item.tabId === selectedTab)
: [];
const selectedTabTitle = tabs?.find(tab => tab.id === selectedTab)?.title ?? 'NA';
const dynamicTabs = Array.isArray(storeInfoData)
? Array.from(new Set(storeInfoData.map(item => item.tabId))).map(id => ({
id,
title: tabs.find(tab => tab.id === id)?.title || `Tab ${id}`
}))
: [];
console.log("listData-----12",storeInfoData,filteredData)
// const getStoreInfo = async () => {
// try {
// const params = {
// "StoreId": "723"
// };
// const config = {
// method: 'post',
// url: 'https://api1.parinaam.in/api/dabur/StoreDNAstoreInfo',
// headers: {
// 'api_key': '9a1f056fecb84eaf8eb4152dda22ab0501955c4f9bbe7daa8780740459fdde7a',
// 'Content-Type': 'application/json'
// },
// data: params
// };
// const response = await axios.request(config);
// const res = response.data?.StoreDNAstoreInfo || {};
// console.log('GET STORE INFO ==> ', JSON.stringify(res));
// const allItems = res && Object.values(res).flat(); // Flattened array
// setStoreInfoData(allItems);
// // Now extract TabId and TabName from array
// const tabData = allItems.map(item => ({
// TabId: item.TabId,
// TabName: item.TabName
// })).filter(item => item.TabId !== undefined && item.TabName);
// // Remove duplicates
// const uniqueTabs = Array.from(
// new Map(tabData.map(item => [`${item.TabId}-${item.TabName}`, item])).values()
// );
// console.log("tabData----", tabData, uniqueTabs)
// let NewObj = {}
// let ourVlue = uniqueTabs.map((item, i) => {
// NewObj[item?.TabName] = allItems?.filter((val) => val?.TabName == item?.TabName)
// })
// console.log("Unique Tabs:", uniqueTabs, JSON.stringify(NewObj));
// console.log("All Items:", JSON.stringify(allItems));
// } catch (error) {
// console.log("❌ store info api error:", error);
// }
// };
const renderTabContent = () => {
switch (selectedTab) {
case 0:
return <SectionListView listData={filteredData} />
case 1:
return <SectionListView listData={filteredData} />
case 2:
return <SectionListView listData={filteredData} />;
default:
return null;
}
};
return (
<View style={[styles.container, { paddingHorizontal: 0 }]}>
<CustomHeader
title={'Store Info'}
leftIcon={IMAGES.leftArrowIcon}
onLeftPress={() => navigation.goBack()} />
<View style={styles.container}>
<View style={styles.tabstyle}>
<ScrollView horizontal showsHorizontalScrollIndicator={false}>
<View style={{ flexDirection: 'row' }}>
{/* {tabs?.map((tab) => ( */}
{dynamicTabs?.map((tab) => (
<TouchableOpacity key={tab.id} style={styles.tabview} activeOpacity={1} onPress={() => setSelectedTab(tab.id)}>
<View style={selectedTab === tab.id ? styles.selecttabView : styles.unselecttabView}>
<Text style={[styles.tabtext, selectedTab === tab.id ? styles.selecttabText : styles.unselecttabText]}>
{tab.title}
</Text>
</View>
</TouchableOpacity>
))}
</View>
</ScrollView>
</View>
<ScrollView horizontal={false} style={{ flex: 1 }}>
<Text style={styles.headerText}>{selectedTabTitle} </Text>
<View style={{ width: '100%' }}>{renderTabContent()}</View>
</ScrollView>
</View>
<Loader visible={loading} />
</View>
);
};
export default StoreInfo;
+319
View File
@@ -0,0 +1,319 @@
import React, { useState, useRef, useEffect } from 'react';
import { View, Text, TouchableOpacity, ScrollView, FlatList } from 'react-native';
import { styles } from './style';
import GlobalTheme, { horizonalLine, Screen } from '../../../theme/theme';
import CustomHeader from '../../../components/CustomHeader';
import IMAGES from '../../../constants/Images';
import axios from 'axios';
import Loader from '../../../constants/Loader';
import { SafeAreaView } from 'react-native-safe-area-context';
const storeinfodata = [
{
tabId: 0,
section: 'Store details',
items: [
{ brand: 'Store Name', name: 'Reliance Smart' },
{ brand: 'Store ID', name: '#98440' },
{ brand: 'Address', name: 'Rabindra Nagar, Delhi 110003' },
{ brand: 'City', name: 'Okhla' },
{ brand: 'Store Size', name: '15002000 sq ft' },
{ brand: 'Average Footfall', name: '10000' },
{ brand: 'Store age', name: '8' },
{ brand: 'Store Ranking', name: '24' },
],
},
{
tabId: 0,
section: 'Dabur Employees',
items: [
{ brand: 'RKAM', name: 'Rajesh Paal Singh' },
{ brand: 'SO', name: 'Soniya Singhal' },
],
},
{
tabId: 0,
section: '3P employees',
items: [
{ brand: 'Promoter Name', name: 'Payal Singh' },
{ brand: 'AM Name', name: 'Soniya Singhal' },
{ brand: 'Supervisor Name', name: 'Soniya Dhankar' },
{ brand: 'City', name: 'Okhla' },
{ brand: 'Promoter duration', name: '2 Year 7 months' },
{ brand: 'Average incentive', name: '5000' },
],
},
// Last Visit Details (tabId: 1)
{
tabId: 1,
section: 'Dabur employee',
items: [
{ brand: 'SO', name: 'Rajesh Paal Singh', date: '23/05/2025', test: 'gxsjhxbas' },
{ brand: 'AH', name: 'Umesh Singh', date: '18/04/2025' },
{ brand: 'KAM', name: 'Rahul Tawde', date: '15/05/2025' },
{ brand: 'Others', name: 'Singhal Singh', date: '06/05/2025' },
],
},
{
tabId: 1,
section: '3P team',
items: [
{ brand: 'Supervisor', name: 'Ashish Talwar', date: '27/05/2025' },
{ brand: 'AM', name: 'Soniya Singhal', date: '23/05/2025' },
],
},
// Competition (tabId: 2)
{
tabId: 2,
section: 'Competition assets',
items: [
{ brand: 'Marico', value: 5 },
{ brand: 'Colgate', value: 1 },
{ brand: 'Godrej', value: 2 },
{ brand: 'HUL', value: 1 },
{ brand: 'XX', value: 2 },
],
},
{
tabId: 2,
section: 'Promoter details',
items: [
{ brand: 'Marico', value: 1 },
{ brand: 'Colgate', value: 1 },
{ brand: 'Godrej', value: 1 },
{ brand: 'HUL', value: 1 },
{ brand: 'XX', value: 0 },
],
},
];
const tabs = [
{ id: 0, title: 'Store Info' },
{ id: 1, title: 'Last visit details' },
{ id: 2, title: 'Competition' },
];
const RenderHeader = ({ columns }) => {
const colWidth = Screen.screenWidth * 0.95 / columns?.length;
return (
null
// <View style={{ flexDirection: 'row', paddingVertical: 6 }}>
// {columns.map((col, index) => (
// <View key={index} style={{ width: colWidth }}>
// <Text style={styles.subheaderText}>{col.toUpperCase()}</Text>
// </View>
// ))}
// </View>
);
};
const RenderItem = ({ item, columns }) => {
const colWidth = Screen.screenWidth * 0.95 / columns?.length;
return (
<View>
<View style={styles.row}>
{columns.map((key, index) => (
<View key={index} style={{ width: colWidth }}>
<Text style={styles.name}>{String(item[key] ?? '')}</Text>
</View>
))}
</View>
<View style={[horizonalLine, { marginVertical: 4 }]} />
</View>
);
};
const SectionListView = ({ listData }) => {
const scrollRef = useRef(null); // shared horizontal scroll ref
console.log("/-", listData)
return (
<FlatList
data={listData}
keyExtractor={(item, index) => item.section + index}
contentContainerStyle={{ paddingBottom: 20 }}
ItemSeparatorComponent={() => <View style={{ paddingVertical: 5 }} />}
renderItem={({ item }) => {
const columns = Object.keys(item.items[0] || {}).filter(
key => item.items.some(obj => obj[key] !== undefined && obj[key] !== null)
);
return (
<View style={styles.section}>
<Text style={styles.sectionTitle}>{item.section}</Text>
<View style={[horizonalLine, { marginVertical: 10 }]} />
{/* Shared horizontal scroll view for header + rows */}
<ScrollView
horizontal
ref={scrollRef}
showsHorizontalScrollIndicator={false}
scrollEventThrottle={16}
>
<View>
<RenderHeader columns={columns} />
{item.items.map((subItem, index) => (
<RenderItem key={index} item={subItem} columns={columns} />
))}
</View>
</ScrollView>
</View>
);
}}
/>
);
};
const StoreInfo = ({ navigation, route }) => {
const { storeData } = route.params || {};
const [loading, setLoading] = useState(false)
const [selectedTab, setSelectedTab] = useState(0);
const [storeInfoData, setStoreInfoData] = useState([])
useEffect(() => {
getStoreInfo()
}, [])
const getStoreInfo = async () => {
setLoading(true);
try {
const params = { StoreId: storeData?.StoreId || "723" };
const config = {
method: 'post',
url: 'https://api1.parinaam.in/api/dabur/StoreDNAstoreInfo',
headers: {
'api_key': '9a1f056fecb84eaf8eb4152dda22ab0501955c4f9bbe7daa8780740459fdde7a',
'Content-Type': 'application/json'
},
data: params
};
const response = await axios.request(config);
const res = response.data?.StoreDNAstoreInfo || {};
// Step 1: Flatten API data
const tempData = [];
Object.entries(res).forEach(([sectionTitle, dataArray]) => {
if (dataArray?.length > 0) {
const tabId = dataArray[0]?.TabId ?? 0;
dataArray.forEach(item => {
const cleanItem = { ...item };
delete cleanItem.TabId;
delete cleanItem.TabName;
console.log("tempData---", item)
const formattedItems = Object.entries(cleanItem).map(([key, value]) => ({
brand: key,
name: String(value)
}));
tempData.push({
tabId,
section: sectionTitle.replace(/_/g, ' ').replace(/\b\w/g, c => c.toUpperCase()),
items: formattedItems
});
});
}
});
// Step 2: Group items by tabId and section
const groupedData = {};
tempData.forEach(entry => {
const key = `${entry.tabId}-${entry.section}`;
if (!groupedData[key]) {
groupedData[key] = {
tabId: entry.tabId,
section: entry.section,
items: []
};
}
groupedData[key].items.push(...entry.items);
});
// let objTemp={}
// groupedData['0-Dabur Employee']?.items
// Step 3: Set to state
setStoreInfoData(Object.values(groupedData));
setLoading(false);
} catch (error) {
console.log("❌ store info api error:", error);
setLoading(false);
}
};
const filteredData = Array.isArray(storeInfoData)
? storeInfoData.filter(item => item.tabId === selectedTab)
: [];
const selectedTabTitle = tabs?.find(tab => tab.id === selectedTab)?.title ?? 'NA';
const dynamicTabs = Array.isArray(storeInfoData)
? Array.from(new Set(storeInfoData.map(item => item.tabId))).map(id => ({
id,
title: tabs.find(tab => tab.id === id)?.title || `Tab ${id}`
}))
: [];
const renderTabContent = () => {
switch (selectedTab) {
case 0:
return <SectionListView listData={filteredData} />
case 1:
return <SectionListView listData={filteredData} />
case 2:
return <SectionListView listData={filteredData} />;
default:
return null;
}
};
return (
<SafeAreaView style={{ flex: 1, backgroundColor: GlobalTheme.colors.primary }}>
<View style={[styles.container, { paddingHorizontal: 0 }]}>
<CustomHeader
title={'Store Info'}
leftIcon={IMAGES.leftArrowIcon}
onLeftPress={() => navigation.goBack()} />
<View style={styles.container}>
<View style={styles.tabstyle}>
<ScrollView horizontal showsHorizontalScrollIndicator={false}>
<View style={{ flexDirection: 'row' }}>
{/* {tabs?.map((tab) => ( */}
{dynamicTabs?.map((tab) => (
<TouchableOpacity key={tab.id} style={styles.tabview} activeOpacity={1} onPress={() => setSelectedTab(tab.id)}>
<View style={selectedTab === tab.id ? styles.selecttabView : styles.unselecttabView}>
<Text style={[styles.tabtext, selectedTab === tab.id ? styles.selecttabText : styles.unselecttabText]}>
{tab.title}
</Text>
</View>
</TouchableOpacity>
))}
</View>
</ScrollView>
</View>
<ScrollView horizontal={false} style={{ flex: 1 }}>
<Text style={styles.headerText}>{selectedTabTitle} </Text>
<View style={{ width: '100%' }}>{renderTabContent()}</View>
</ScrollView>
</View>
<Loader visible={loading} />
</View>
</SafeAreaView>
);
};
export default StoreInfo;
+115
View File
@@ -0,0 +1,115 @@
import { StyleSheet } from 'react-native';
import { GlobalTheme, Screen, shadow } from '../../../theme';
export const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: GlobalTheme.colors.white,
paddingHorizontal: 10
},
// tabs
tabview: {
alignItems: 'center',
justifyContent: 'space-between',
marginVertical: 10,
flexDirection: 'row',
},
tabstyle: {
flexDirection: 'row',
justifyContent: 'space-between',
borderColor: GlobalTheme.colors.lightblue,
borderWidth: 1.5,
borderRadius: GlobalTheme.borderRadius.xxlg,
marginTop: 10,
paddingHorizontal: 10,
},
tabtext: {
overflow: 'hidden',
color:'#000'
},
selecttabView: {
backgroundColor: GlobalTheme.colors.primary,
paddingVertical: 8,
paddingHorizontal: 15,
borderRadius: GlobalTheme.borderRadius.lgg
},
selecttabText: {
color: GlobalTheme.colors.white,
fontSize: GlobalTheme.typography.fontSize.small,
fontWeight: GlobalTheme.typography.fontWeight.regular,
textAlign: 'center'
},
unselecttabView: {
paddingVertical: 8,
paddingHorizontal: 15,
borderRadius: GlobalTheme.borderRadius.md
},
unselecttabText: {
color: GlobalTheme.colors.lightbluetext,
fontSize: GlobalTheme.typography.fontSize.xsmall,
fontWeight: GlobalTheme.typography.fontWeight.regular,
textAlign: 'center'
},
// comp
section: {
backgroundColor: GlobalTheme.colors.white,
padding: 12,
marginHorizontal: 5,
borderRadius: 8,
elevation: 2,
width: Screen.screenWidth * 0.92,
marginVertical: 5,
borderWidth: 1,
borderColor: GlobalTheme.colors.lightblueborder,
// ...shadow,
},
sectionTitle: {
color:'#000',
fontWeight: GlobalTheme.typography.fontWeight.bold,
fontSize: GlobalTheme.typography.fontSize.small,
},
row: {
flexDirection: 'row',
justifyContent: 'space-between',
paddingVertical: 4
},
brand: {
fontSize: GlobalTheme.typography.fontSize.xsmall,
color: GlobalTheme.colors.black,
fontWeight: GlobalTheme.typography.fontWeight.medium,
},
value: {
fontSize: GlobalTheme.typography.fontSize.xsmall,
fontWeight: GlobalTheme.typography.fontWeight.medium,
},
name: {
fontSize: GlobalTheme.typography.fontSize.xsmall,
fontWeight: GlobalTheme.typography.fontWeight.regular,
flexWrap:'wrap',
color:'#000'
},
date: {
fontSize: GlobalTheme.typography.fontSize.xsmall,
fontWeight: GlobalTheme.typography.fontWeight.regular,
flexWrap:'wrap'
},
subheaderText:{
fontSize: GlobalTheme.typography.fontSize.xsmall,
fontWeight: GlobalTheme.typography.fontWeight.regular,
flexWrap:'wrap',
color : GlobalTheme.colors.gray,
textTransform: 'capitalize',
},
headerText:{
fontSize: GlobalTheme.typography.fontSize.medium,
fontWeight: GlobalTheme.typography.fontWeight.medium,
flexWrap:'wrap',
color : GlobalTheme.colors.black,
paddingVertical:10,
paddingHorizontal:10
}
});
@@ -0,0 +1,28 @@
import React from 'react';
import { View, Text, ImageBackground, Image, TouchableOpacity } from 'react-native';
import IMAGES from '../../../constants/Images';
import { SafeAreaView } from 'react-native-safe-area-context';
import { styles } from './style';
const Welcome = ({ navigation }) => {
const goToDashboard=()=>{
navigation.reset({ index: 0, routes: [{ name: 'Dashboard' }] })
}
return (
<View style={styles.container}>
<ImageBackground source={IMAGES.WelcomeBackground} style={styles.background} resizeMode='cover'>
<View style={styles.content}>
<Image source={IMAGES.Welcomelogo} style={styles.illustration} />
<Text style={styles.title}>Welcome to {"\n"} Performics Store DNA</Text>
<Text style={styles.subtitle}>Get quick access to store feedback and performance reports.</Text>
<TouchableOpacity style={styles.button} onPress={() => goToDashboard()} >
<Text style={styles.buttonText}>Continue</Text>
</TouchableOpacity>
</View>
</ImageBackground>
</View>
);
};
export default Welcome;
@@ -0,0 +1,61 @@
import { Dimensions, StyleSheet } from 'react-native';
import { GlobalTheme, Screen, shadow } from '../../../theme';
const { width, height } = Dimensions.get('window');
export const styles = StyleSheet.create({
containermain: {
flex: 1,
// backgroundColor: 'red',
},
container: {
flex: 1,
},
background: {
flex: 1,
},
content: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
paddingHorizontal: 20,
},
illustration: {
width: 200,
height: 200,
marginBottom: 30,
resizeMode:"contain"
},
title: {
fontSize: GlobalTheme.typography.fontSize.large,
fontWeight: 'bold',
color: GlobalTheme.colors.primary,
marginBottom: 10,
textAlign: 'center',
},
subtitle: {
fontSize: 14,
color: GlobalTheme.colors.primary,
textAlign: 'center',
marginBottom: 30,
paddingHorizontal: 10,
},
button: {
backgroundColor: GlobalTheme.colors.white,
paddingVertical: 12,
paddingHorizontal: 30,
borderRadius: 30,
borderColor:'#91A2CB',
borderWidth:1
},
buttonText: {
color: GlobalTheme.colors.primary,
fontSize: GlobalTheme.typography.fontSize.xsmall,
// fontWeight: '600',
},
})
+4
View File
@@ -0,0 +1,4 @@
import GlobalTheme from './theme';
import { shadow , Screen , horizonalLine } from './theme';
export {GlobalTheme, Screen , shadow, horizonalLine};
+87
View File
@@ -0,0 +1,87 @@
import { Dimensions, Platform } from 'react-native';
const { width: screenWidth, height: screenHeight } = Dimensions.get('window');
export const Screen = {
screenWidth,
screenHeight,
isAndroid: Platform.OS === 'android',
isIOS: Platform.OS === 'ios',
};
const GlobalTheme = {
colors: {
// Primary Colors
primary: '#113F8C', // Main color for buttons, headers
secondary: '#2357C6',
lightbluetext:'#7F83AB',
text: '#333333', // Text color for most content
lightblue:'#E2E7F2',
lightblueborder:'#ECECEC',
bluebgcolor :'#EAF0F4',
// Additional Colors
success: '#4caf50', // Success or positive action color
yellow: '#FFD661', // Warning or alert color
error: '#ff5252', // Error or negative action color
info: '#2196f3', // Information color
// Grayscale
white: '#ffffff',
lightGray: '#F1F1F1',
gray: '#676767',
darkGray: '#555555',
black: '#000000',
},
typography: {
// Define typography styles here (e.g., font family, sizes, line heights, etc.)
fontFamily: 'Regular',
fontSize: {
xxsmall: 12,
xsmall :14,
small: 16,
medium: 18,
large: 20,
},
fontWeight: {
regular: '400',
medium: '500',
bold: '700',
},
},
spacing: {
// Define spacing units (e.g., margin and padding) for consistent layout
xs: 4,
sm: 8,
md: 16,
lg: 24,
xl: 32,
},
borderRadius: {
// Define border radius values for consistent UI elements
sm: 5,
md: 10,
lg: 15,
lgg: 20,
xlg: 25,
xxlg: 35,
},
// Add other global theme properties as needed
};
export default GlobalTheme;
export const shadow = {
shadowColor: '#00000029',
shadowOffset: {width: 5, height: 5},
shadowOpacity: 0.8,
shadowRadius: 10,
elevation: 20,
};
export const horizonalLine = {
backgroundColor :'#E2E7F2',
height:1.5,
width: '100%',
};
+21
View File
@@ -0,0 +1,21 @@
import { StatusBar} from 'react-native';
import React from 'react';
import {GlobalTheme} from '../theme';
import { SafeAreaView } from 'react-native-safe-area-context';
const MyStatusBar = () => {
return (
<SafeAreaView
style={{
backgroundColor: GlobalTheme.colors.primary,
}}>
<StatusBar
translucent={false}
backgroundColor={GlobalTheme.colors.black}
barStyle={'light-content'}
/>
</SafeAreaView>
);
};
export default MyStatusBar;
View File
+11
View File
@@ -0,0 +1,11 @@
// responsive.js
import { Dimensions, PixelRatio } from 'react-native';
const { width: SCREEN_WIDTH, height: SCREEN_HEIGHT } = Dimensions.get('window');
const scale = SCREEN_WIDTH / 375; // base iPhone 11 width
export const normalize = (size) => {
const newSize = size * scale;
return Math.round(PixelRatio.roundToNearestPixel(newSize));
};