334 lines
13 KiB
JavaScript
334 lines
13 KiB
JavaScript
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;
|