1657 lines
53 KiB
JavaScript
1657 lines
53 KiB
JavaScript
import React from 'react';
|
|
import {
|
|
Alert,
|
|
Button,
|
|
ScrollView,
|
|
StyleSheet,
|
|
Text,
|
|
TextInput,
|
|
TouchableOpacity,
|
|
View,
|
|
} from 'react-native';
|
|
// Reduxs
|
|
import { connect } from 'react-redux';
|
|
import { setContact } from '../Store/Actions/contacts';
|
|
// Helpers
|
|
import Helpers from '../Lib/Helpers';
|
|
// Translations
|
|
import i18n from '../../I18n';
|
|
// Icons
|
|
import FontAwesome5 from 'react-native-vector-icons/FontAwesome5';
|
|
// Style
|
|
import styles from '../../Resources/Styles/ContactDetailsStyle';
|
|
// UI Elements
|
|
import { ButtonGroup, CheckBox, Overlay } from '@rneui/themed';
|
|
// Flash Messages
|
|
import { showMessage } from 'react-native-flash-message';
|
|
// Picker
|
|
import { Picker } from '@react-native-picker/picker';
|
|
// * Select List
|
|
import SectionedMultiSelect from 'react-native-sectioned-multi-select';
|
|
// vCard parser
|
|
import vCard from 'vcard-parser';
|
|
// BarCode Scanner
|
|
import { BarCodeScanner } from 'expo-barcode-scanner';
|
|
// FormModel
|
|
import { InputStatus } from '../Enums/InputStatus';
|
|
import { InputType } from '../Enums/InputType';
|
|
import { Contact, Form } from './../../Database';
|
|
import { InputData } from './../Models/Contact';
|
|
|
|
function mapStateToProps(state) {
|
|
return {
|
|
locale: state.locale.locale,
|
|
contacts: state.contacts,
|
|
currentUser: state.app.currentUser,
|
|
mainColor: state.app.mainColor,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* @typedef {(user: import('./../Models/User').User, data: import('./../Models/Contact').Contact) => any} setContact
|
|
*/
|
|
|
|
const mapDispatchToProps = dispatch => {
|
|
return {
|
|
setContact: (user, data) => dispatch(setContact(user, data)),
|
|
};
|
|
};
|
|
class ContactDetailsScreen extends React.Component {
|
|
constructor(props) {
|
|
super(props);
|
|
|
|
// * This is to hold array of number for multiple inputs selects
|
|
const multipleSelectInputs = Object.fromEntries(
|
|
new Map(
|
|
Form.getAll()
|
|
.filter(form => form.type === InputType.SELECT_MULTIPLE)
|
|
.map(form => [`input${form.id}`, { show: false, selectedItems: [] }]),
|
|
),
|
|
);
|
|
|
|
const { contactId } = props.route.params;
|
|
|
|
const paramContact = Contact.get(this.props.currentUser.id, contactId);
|
|
|
|
if (paramContact) {
|
|
this.state.action = 'edit';
|
|
}
|
|
|
|
// * Init Contact data list
|
|
const contact = paramContact
|
|
? this.initContact(paramContact)
|
|
: this.initNewContact(Contact.new());
|
|
this.state.contact = contact;
|
|
|
|
// * Setup All inputs State
|
|
this.state.allInputs = Form.getAll().map(form => {
|
|
const isDefault = form.status === InputStatus.DEFAULT;
|
|
const inputData = Object.assign({}, InputData);
|
|
inputData.title = form.placeholder[this.props.locale];
|
|
inputData.required = form.required;
|
|
inputData.formSlug = form.slug;
|
|
inputData.type = form.type;
|
|
inputData.value =
|
|
(!isDefault
|
|
? contact[form.slug]
|
|
: contact.data.find(input => input.formSlug === form.slug)?.value) ??
|
|
'';
|
|
if (form.type === InputType.CHECKBOX) {
|
|
inputData.value =
|
|
(!isDefault
|
|
? contact[form.slug]
|
|
: contact.data.find(input => input.formSlug === form.slug)
|
|
?.value) ?? [];
|
|
}
|
|
// * Init Multiple selete State
|
|
if (form.type === InputType.SELECT_MULTIPLE) {
|
|
const details = form.details.map(
|
|
detail => JSON.parse(detail)[i18n.locale] ?? null,
|
|
);
|
|
multipleSelectInputs[`input${form.id}`].selectedItems = (
|
|
inputData.value ?? []
|
|
)
|
|
.map((selectedDetail, detailIndex) => details.indexOf(selectedDetail))
|
|
.filter(detailIndex => detailIndex !== -1);
|
|
console.debug('SELECT_MULTIPLE', inputData.value);
|
|
console.debug(multipleSelectInputs[`input${form.id}`]);
|
|
}
|
|
return inputData;
|
|
});
|
|
this.state.multipleSelectInputs = multipleSelectInputs;
|
|
}
|
|
|
|
// * --- STATE ---
|
|
state = {
|
|
/** @type {'add'|'edit} */
|
|
action: 'add',
|
|
|
|
/** @type {0|1} 0 is VISITS 1, is NOTES */
|
|
selectedVisitsOrNoteTab: 0,
|
|
|
|
// * -- VISITS
|
|
selectedVisitIndex: 0,
|
|
showNewVisit: false,
|
|
tmpVisitNote: '',
|
|
newVisitTitle: '',
|
|
|
|
// * -- VCARD
|
|
/** @type {boolean|null} TRUE ok, FALSE refused, NULL not asked */
|
|
hasPermission: null,
|
|
vCardData: {
|
|
name: '',
|
|
firstname: '',
|
|
email: '',
|
|
company: '',
|
|
address: '',
|
|
phone: '',
|
|
},
|
|
/** @type {string|null} */
|
|
parsedType: null,
|
|
/** @type {string[]} */
|
|
parsedDatas: [],
|
|
/** @type {boolean} */
|
|
showQRcodevCard: false,
|
|
/** @type {boolean} */
|
|
showDynContact: false,
|
|
/** @type {boolean} */
|
|
scanned: false,
|
|
};
|
|
|
|
// * --- METHODS
|
|
|
|
// * - CONTACT METHODS
|
|
saveContact() {
|
|
/** @type {typeof import('./../Models/Contact').ContactData} */
|
|
const contact = Object.assign({}, this.state.contact);
|
|
|
|
const defaultForms = Form.getAll(InputStatus.DEFAULT);
|
|
const lockedForms = Form.getAll(InputStatus.LOCKED);
|
|
|
|
console.debug('save Contact');
|
|
|
|
let error = '';
|
|
|
|
// * VALIDATE SYSTEM FIELDS
|
|
if (
|
|
!contact.email ||
|
|
contact.email.length < 5 ||
|
|
contact.email.length >= 255 ||
|
|
!Helpers.isEmailValid(contact.email)
|
|
) {
|
|
console.debug('invalid prop email');
|
|
error = error.concat(i18n.t('error.invalid_email_address') + '.\n');
|
|
}
|
|
const lockedFields = ['firstname', 'lastname', 'phone'];
|
|
lockedFields.forEach(fieldName => {
|
|
const form = lockedForms.find(inputForm => inputForm.name === fieldName);
|
|
if (!form) {
|
|
throw new Error(`failed to get form ${fieldName}`);
|
|
}
|
|
if (contact[fieldName]?.length) {
|
|
contact[fieldName] = contact[fieldName].trim();
|
|
}
|
|
if (form.required && !contact[fieldName]?.length) {
|
|
error = error.concat(
|
|
i18n.t('contact_details_s.input_required', {
|
|
placeholder: form.placeholder[i18n.locale],
|
|
}) + '.\n',
|
|
);
|
|
}
|
|
if (contact[fieldName] && contact[fieldName]?.length >= 255) {
|
|
console.debug(`invalid prop invalid_${fieldName}`);
|
|
error = error.concat(i18n.t(`contact.invalid_${fieldName}`) + '.\n');
|
|
}
|
|
});
|
|
|
|
contact.data.forEach(input => {
|
|
const formToValidate = defaultForms.find(
|
|
form => form.slug === input.formSlug,
|
|
);
|
|
if (formToValidate.required && !input?.value?.length) {
|
|
console.debug('invalid data ', input.formSlug);
|
|
error = error.concat(
|
|
i18n.t('contact_details_s.input_required', {
|
|
placeholder: formToValidate.placeholder[i18n.locale],
|
|
}) + '.\n',
|
|
);
|
|
}
|
|
if (
|
|
formToValidate.type === InputType.EMAIL ||
|
|
formToValidate.type === InputType.PHONE ||
|
|
formToValidate.type === InputType.TEXT
|
|
) {
|
|
if (input?.value?.length) {
|
|
input.value = input.value.trim();
|
|
}
|
|
if (input?.value?.length > 255) {
|
|
console.debug('invalid data ', input.formSlug);
|
|
error = error.concat(
|
|
i18n.t('contact_details_s.input_too_long', {
|
|
placeholder: formToValidate.placeholder[i18n.locale],
|
|
}) + '.\n',
|
|
);
|
|
}
|
|
}
|
|
if (
|
|
input?.value?.length &&
|
|
formToValidate.type === InputType.EMAIL &&
|
|
!Helpers.isEmailValid(input.value)
|
|
) {
|
|
console.debug('invalid data email ', input.formSlug);
|
|
error = error.concat(
|
|
i18n.t('contact_details_s.input_email', {
|
|
placeholder: formToValidate.placeholder[i18n.locale],
|
|
}) + '.\n',
|
|
);
|
|
}
|
|
});
|
|
|
|
if (error.length > 0) {
|
|
Alert.alert(i18n.t('global.warning') + ' !', error, [{ text: 'OK' }], {
|
|
cancelable: false,
|
|
});
|
|
return;
|
|
}
|
|
|
|
console.debug(contact.data);
|
|
|
|
// * Stringify fields to store them
|
|
contact.visits = JSON.stringify(contact.visits);
|
|
contact.data = JSON.stringify(contact.data);
|
|
contact.timestamp = new Date();
|
|
contact.synced = false;
|
|
|
|
/** @type {setContact} */
|
|
const setContactHook = this.props.setContact;
|
|
|
|
setContactHook(this.props.currentUser, contact);
|
|
|
|
if (this.state.action === 'edit') {
|
|
showMessage({
|
|
message: i18n.t('contact_details_s.contact_saved') + '.',
|
|
type: 'success',
|
|
duration: 500,
|
|
});
|
|
} else {
|
|
setTimeout(() => {
|
|
this.props.navigation.navigate('ContactScreen', {
|
|
contactAdded: true,
|
|
});
|
|
}, 1500);
|
|
}
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param {import('./../Models/Contact').Contact} contact
|
|
* @returns {import('./../Models/Contact').ContactData}
|
|
*/
|
|
initContact(contact) {
|
|
contact = contact.toJSON();
|
|
// * Stringify fields to store them
|
|
contact.visits = JSON.parse(contact.visits);
|
|
/** @type {Map<string,import('./../Models/Contact').InputDataType>} */
|
|
const originalData = new Map(
|
|
[...JSON.parse(contact.data)].map(input => [input.formSlug, input]),
|
|
);
|
|
contact.data = Form.getAll(InputStatus.DEFAULT).map(
|
|
this.initField.bind(this),
|
|
);
|
|
console.debug(contact.data);
|
|
contact.data.map(input => {
|
|
const originalInput = originalData.get(input.formSlug);
|
|
// * Had this input before ?
|
|
if (originalInput !== undefined) {
|
|
// * Copy only if it is the same type
|
|
input.value =
|
|
input.type === originalInput.type ? originalInput.value : input.value;
|
|
}
|
|
});
|
|
return contact;
|
|
}
|
|
|
|
/**
|
|
* Get new instance of Contact
|
|
* @param {import('./../Models/Contact').ContactData} contact
|
|
* @returns {import('./../Models/Contact').ContactData}
|
|
*/
|
|
initNewContact(contact) {
|
|
// * Init DEFAULT Fields
|
|
contact.data = Form.getAll(InputStatus.DEFAULT).map(
|
|
this.initField.bind(this),
|
|
);
|
|
contact.visits = [];
|
|
contact.notes = '';
|
|
contact.app_user_id = this.props.currentUser.id;
|
|
return contact;
|
|
}
|
|
|
|
/**
|
|
* Setup input field
|
|
* @param {import('./../Models/Form').Form} form
|
|
* @returns {import('./../Models/Contact').InputDataType}
|
|
*/
|
|
initField(form) {
|
|
const inputData = Object.assign({}, InputData);
|
|
inputData.title = form.placeholder[this.props.locale];
|
|
inputData.required = form.required;
|
|
inputData.formSlug = form.slug;
|
|
inputData.type = form.type;
|
|
inputData.value = '';
|
|
if (
|
|
form.type === InputType.CHECKBOX ||
|
|
form.type === InputType.SELECT_MULTIPLE
|
|
) {
|
|
inputData.value = [];
|
|
}
|
|
return inputData;
|
|
}
|
|
|
|
// * - VISITS METHODS
|
|
|
|
newVisit() {
|
|
/** @type {typeof import('./../Models/Contact').ContactData} */
|
|
const contact = this.state.contact;
|
|
contact.visits.unshift({ title: this.state.newVisitTitle, note: '' });
|
|
this.setState({
|
|
contact: contact,
|
|
showNewVisit: false,
|
|
selectedVisitIndex: 0,
|
|
});
|
|
}
|
|
|
|
deleteVisit() {
|
|
Alert.alert(
|
|
i18n.t('global.confirmation'),
|
|
i18n.t('contact_details_s.confirm_visit_delete'),
|
|
[
|
|
{
|
|
text: i18n.t('global.no'),
|
|
onPress: () => console.debug('Cancel Pressed'),
|
|
style: 'cancel',
|
|
},
|
|
{
|
|
text: i18n.t('global.yes'),
|
|
onPress: () => {
|
|
/** @type {typeof import('./../Models/Contact').ContactData} */
|
|
const contact = this.state.contact;
|
|
contact.visits.splice(this.state.selectedVisitIndex, 1);
|
|
if (contact.visits.length === 0) {
|
|
this.setState({
|
|
contact: contact,
|
|
selectedVisitIndex: 0,
|
|
});
|
|
} else {
|
|
this.setState({
|
|
contact: contact,
|
|
selectedVisitIndex: 0,
|
|
});
|
|
}
|
|
},
|
|
},
|
|
],
|
|
{ cancelable: false },
|
|
);
|
|
}
|
|
|
|
// * END - VISITS METHODS
|
|
|
|
/** @type {import('expo-barcode-scanner').BarCodeScannedCallback} */
|
|
handleBarCodeScanned({ type, data }) {
|
|
this.setState({ scanned: true });
|
|
if (!data.includes('VCARD')) {
|
|
Alert.alert(i18n.t('contact_details_s.qrcode_not_vcard_format'));
|
|
return;
|
|
}
|
|
/** @type {{ }} */
|
|
let datas = vCard.parse(data);
|
|
/** @type {string[]} */
|
|
const tmpDatas = [];
|
|
if (datas.n !== undefined) {
|
|
const nx = Object.assign(
|
|
{ n1: [{ value: datas.n[0].value[0] }] },
|
|
{ n2: [{ value: datas.n[0].value[1] }] },
|
|
);
|
|
datas = Object.assign(datas, nx);
|
|
}
|
|
if (datas.adr !== undefined) {
|
|
const tmp = datas.adr[0].value;
|
|
let tmpadr = '';
|
|
for (let i = 0; i < tmp.length; i++) {
|
|
if (tmp[i].length > 0) {
|
|
if (tmpadr.length > 0) {
|
|
tmpadr = tmpadr + ', ' + tmp[i];
|
|
} else {
|
|
tmpadr = tmp[i];
|
|
}
|
|
}
|
|
}
|
|
datas.adr = [{ value: tmpadr }];
|
|
}
|
|
Object.keys(datas).map((objectKey, index) => {
|
|
if (
|
|
objectKey === 'email' ||
|
|
objectKey === 'adr' ||
|
|
objectKey === 'tel' ||
|
|
objectKey === 'org' ||
|
|
objectKey === 'n1' ||
|
|
objectKey === 'n2'
|
|
) {
|
|
let value = datas[objectKey][0].value;
|
|
if (typeof value === 'string') {
|
|
tmpDatas.push(value);
|
|
}
|
|
}
|
|
});
|
|
this.setState({
|
|
parsedType: 'vcard',
|
|
parsedDatas: tmpDatas,
|
|
showQRcodevCard: false,
|
|
showDynContact: true,
|
|
});
|
|
}
|
|
|
|
// * END --- METHODS
|
|
|
|
componentDidMount() {
|
|
const { action } = this.props.route.params;
|
|
this.setState({ action: action === 'edit' ? 'edit' : 'add' });
|
|
|
|
// BarCodeScanner.requestPermissionsAsync().then(res => {
|
|
// this.setState({ hasPermission: res.status === 'granted' });
|
|
// });
|
|
}
|
|
|
|
render() {
|
|
return (
|
|
<View style={styles.container}>
|
|
{/* LEFT SIDE -- FORMULARY VIEW */}
|
|
<ScrollView style={{ flex: 1 }}>
|
|
{/* ALL INPUTS : They are sorted on sync by server */}
|
|
{this.renderFormulary(Form.getAll()).map((input, inputIndex) => {
|
|
input.key = inputIndex;
|
|
return input;
|
|
})}
|
|
{/* BOTTOM SPACE */}
|
|
<View style={{ height: 50 }} />
|
|
</ScrollView>
|
|
{/* RIGHT SIDE -- VISITS AND NOTES */}
|
|
<View style={{ flex: 1, margin: 5 }}>
|
|
{/* VISITS AND NOTES */}
|
|
<View style={{ flex: 1 }}>
|
|
<ButtonGroup
|
|
buttons={[
|
|
i18n.t('contact_details_s.visits'),
|
|
i18n.t('contact_details_s.notes'),
|
|
]}
|
|
selectedButtonStyle={{ backgroundColor: this.props.mainColor }}
|
|
containerStyle={{ height: 30 }}
|
|
selectedIndex={this.state.selectedVisitsOrNoteTab}
|
|
onPress={index =>
|
|
this.setState({ selectedVisitsOrNoteTab: index })
|
|
}
|
|
/>
|
|
{/* VISITS */}
|
|
{this.state.selectedVisitsOrNoteTab === 0 && (
|
|
<View style={{ flex: 1 }}>
|
|
<View style={{ flexDirection: 'row' }}>
|
|
{this.state.contact.visits.length > 0 && (
|
|
<View style={{ flex: 2, flexDirection: 'row' }}>
|
|
<FontAwesome5
|
|
name="sort-down"
|
|
size={20}
|
|
color={'black'}
|
|
style={{
|
|
zIndex: 1,
|
|
position: 'absolute',
|
|
right: 28,
|
|
top: 22,
|
|
}}
|
|
/>
|
|
<Picker
|
|
style={styles.select}
|
|
selectedValue={this.state.selectedVisitIndex}
|
|
onValueChange={(itemValue, itemIndex) => {
|
|
this.setState({
|
|
selectedVisitIndex: itemIndex,
|
|
});
|
|
}}>
|
|
{this.state.contact.visits.map((item, index) => {
|
|
return (
|
|
<Picker.Item
|
|
label={item.title}
|
|
value={index}
|
|
key={index}
|
|
/>
|
|
);
|
|
})}
|
|
</Picker>
|
|
</View>
|
|
)}
|
|
{this.state.contact.visits.length === 0 && (
|
|
<View
|
|
style={{
|
|
flex: 1,
|
|
flexDirection: 'row',
|
|
justifyContent: 'center',
|
|
alignItems: 'center',
|
|
}}>
|
|
<Text>{i18n.t('contact_details_s.no_visits')}.</Text>
|
|
</View>
|
|
)}
|
|
<View
|
|
style={{
|
|
flex: 1,
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
justifyContent: 'space-evenly',
|
|
margin: 10,
|
|
}}>
|
|
<TouchableOpacity
|
|
style={styles.button}
|
|
onPress={() => {
|
|
const localDatetimeString = new Date().toLocaleString(
|
|
this.props.locale,
|
|
);
|
|
this.setState({
|
|
showNewVisit: true,
|
|
newVisitTitle: localDatetimeString.substring(
|
|
0,
|
|
localDatetimeString.length - 3,
|
|
),
|
|
});
|
|
}}>
|
|
<FontAwesome5
|
|
name="plus-circle"
|
|
size={20}
|
|
color={this.props.mainColor}
|
|
/>
|
|
</TouchableOpacity>
|
|
{this.state.contact.visits.length > 0 && (
|
|
<TouchableOpacity
|
|
style={styles.button}
|
|
onPress={() => this.deleteVisit()}>
|
|
<FontAwesome5
|
|
name="trash"
|
|
size={20}
|
|
color={this.props.mainColor}
|
|
/>
|
|
</TouchableOpacity>
|
|
)}
|
|
</View>
|
|
</View>
|
|
{this.state.contact.visits.length > 0 && (
|
|
<TextInput
|
|
style={styles.textArea}
|
|
underlineColorAndroid="transparent"
|
|
placeholder={i18n.t('contact_details_s.notes') + '...'}
|
|
placeholderTextColor="grey"
|
|
multiline={true}
|
|
value={
|
|
this.state.contact.visits[this.state.selectedVisitIndex]
|
|
.note
|
|
}
|
|
onChangeText={text => {
|
|
/** @type {typeof import('./../Models/Contact').ContactData} */
|
|
const contact = this.state.contact;
|
|
contact.visits[this.state.selectedVisitIndex].note = text;
|
|
this.setState({ contact: contact });
|
|
}}
|
|
/>
|
|
)}
|
|
</View>
|
|
)}
|
|
{/* NOTES */}
|
|
{this.state.selectedVisitsOrNoteTab === 1 && (
|
|
<TextInput
|
|
style={styles.textArea}
|
|
underlineColorAndroid="transparent"
|
|
placeholder={i18n.t('contact_details_s.notes') + '...'}
|
|
placeholderTextColor="grey"
|
|
multiline={true}
|
|
value={this.state.contact.notes}
|
|
onChangeText={text => {
|
|
/** @type {typeof import('./../Models/Contact').ContactData} */
|
|
const contact = this.state.contact;
|
|
contact.notes = text;
|
|
this.setState({ contact: contact });
|
|
}}
|
|
/>
|
|
)}
|
|
</View>
|
|
{/* ACTIONS BUTTONS on BOTTOM */}
|
|
<View
|
|
style={{ flexDirection: 'row', justifyContent: 'space-evenly' }}>
|
|
{this.state.action === 'add' && (
|
|
<Button
|
|
title="QRCode vCard"
|
|
onPress={() => this.setState({ showQRcodevCard: true })}
|
|
color={this.props.mainColor}
|
|
// THIS IS NOT AVAILABLE FOR NOW
|
|
disabled={true}
|
|
opacity={0}
|
|
/>
|
|
)}
|
|
<Button
|
|
title={i18n.t('global.save')}
|
|
onPress={() => this.saveContact()}
|
|
color={this.props.mainColor}
|
|
/>
|
|
</View>
|
|
</View>
|
|
{/* NEW VISIT OVERLAY */}
|
|
<Overlay
|
|
isVisible={this.state.showNewVisit}
|
|
windowBackgroundColor="rgba(255, 255, 255, .9)"
|
|
overlayBackgroundColor="white"
|
|
width={400}
|
|
height={300}>
|
|
<View
|
|
style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
|
|
<Text style={{ marginBottom: 10 }}>
|
|
{i18n.t('contact_details_s.visit_name')} :
|
|
</Text>
|
|
<TextInput
|
|
placeholder={i18n.t('contact_details_s.visit_name')}
|
|
value={this.state.newVisitTitle}
|
|
onChangeText={text => this.setState({ newVisitTitle: text })}
|
|
style={{
|
|
borderWidth: 1,
|
|
borderColor: 'lightgrey',
|
|
width: 300,
|
|
marginBottom: 15,
|
|
paddingLeft: 10,
|
|
}}
|
|
/>
|
|
<View
|
|
style={{
|
|
flexDirection: 'row',
|
|
justifyContent: 'space-evenly',
|
|
alignItems: 'center',
|
|
width: 400,
|
|
}}>
|
|
<Button
|
|
title={i18n.t('global.cancel')}
|
|
onPress={() => this.setState({ showNewVisit: false })}
|
|
color={this.props.mainColor}
|
|
/>
|
|
<Button
|
|
title={i18n.t('global.add')}
|
|
onPress={() => this.newVisit()}
|
|
color={this.props.mainColor}
|
|
/>
|
|
</View>
|
|
</View>
|
|
</Overlay>
|
|
{/* VCARD QR CODE OVERLAY -- TAKE PICTURE */}
|
|
<Overlay
|
|
isVisible={this.state.showQRcodevCard}
|
|
windowBackgroundColor="rgba(255, 255, 255, .9)"
|
|
overlayBackgroundColor="white"
|
|
width={400}
|
|
height={300}>
|
|
<View
|
|
style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
|
|
{this.state.hasPermission === true && (
|
|
<BarCodeScanner
|
|
onBarCodeScanned={
|
|
this.state.scanned ? undefined : this.handleBarCodeScanned
|
|
}
|
|
barCodeTypes={[BarCodeScanner.Constants.BarCodeType.qr]}
|
|
style={{ flex: 1, width: 380, height: 200 }}
|
|
/>
|
|
)}
|
|
{this.state.hasPermission === false && (
|
|
<Text>{i18n.t('error.camera_permission_denied')}.</Text>
|
|
)}
|
|
{this.state.hasPermission === null && (
|
|
<Text>{i18n.t('error.pending_camera_permission')}.</Text>
|
|
)}
|
|
<View
|
|
style={{
|
|
flexDirection: 'row',
|
|
justifyContent: 'space-evenly',
|
|
alignItems: 'center',
|
|
width: 400,
|
|
}}>
|
|
<Button
|
|
title={i18n.t('global.cancel')}
|
|
onPress={() =>
|
|
this.setState({ showQRcodevCard: false, scanned: false })
|
|
}
|
|
color={this.props.mainColor}
|
|
/>
|
|
{this.state.scanned && (
|
|
<Button
|
|
title={i18n.t('global.retry')}
|
|
onPress={() => this.setState({ scanned: false })}
|
|
color={this.props.mainColor}
|
|
/>
|
|
)}
|
|
</View>
|
|
</View>
|
|
</Overlay>
|
|
{/* VCARD QR CODE OVERLAY -- SCAN RESULT */}
|
|
<Overlay
|
|
isVisible={this.state.showDynContact}
|
|
windowBackgroundColor="rgba(255, 255, 255, .9)"
|
|
overlayBackgroundColor="white"
|
|
width={400}
|
|
height={400}>
|
|
<View
|
|
style={{
|
|
flex: 1,
|
|
alignItems: 'center',
|
|
justifyContent: 'space-evenly',
|
|
}}>
|
|
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
|
|
<Text style={{ flex: 1, paddingLeft: 10 }}>
|
|
{i18n.t('global.lastname')}
|
|
</Text>
|
|
<Picker
|
|
selectedValue={this.state.vCardData.name}
|
|
style={{ height: 50, width: 250 }}
|
|
onValueChange={(itemValue, itemIndex) => {
|
|
const vCardData = this.state.vCardData;
|
|
vCardData.name = itemValue;
|
|
this.setState({ vCardData: vCardData });
|
|
}}>
|
|
<Picker.Item label="" value="0" key={0} />
|
|
{this.state.parsedType === 'vcard' &&
|
|
this.state.parsedDatas.map((item, index) => {
|
|
return (
|
|
<Picker.Item
|
|
label={item}
|
|
value={item}
|
|
key={(index + 1).toString()}
|
|
/>
|
|
);
|
|
})}
|
|
</Picker>
|
|
</View>
|
|
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
|
|
<Text style={{ flex: 1, paddingLeft: 10 }}>
|
|
{i18n.t('global.firstname')}
|
|
</Text>
|
|
<Picker
|
|
selectedValue={this.state.parsedFirstname}
|
|
style={{ height: 50, width: 250 }}
|
|
onValueChange={(itemValue, itemIndex) =>
|
|
this.setState({ parsedFirstname: itemValue })
|
|
}>
|
|
<Picker.Item label="" value="0" key={0} />
|
|
{this.state.parsedType === 'vcard' &&
|
|
this.state.parsedDatas.map((item, index) => {
|
|
return (
|
|
<Picker.Item
|
|
label={item}
|
|
value={item}
|
|
key={(index + 1).toString()}
|
|
/>
|
|
);
|
|
})}
|
|
</Picker>
|
|
</View>
|
|
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
|
|
<Text style={{ flex: 1, paddingLeft: 10 }}>
|
|
{i18n.t('global.email')}
|
|
</Text>
|
|
<Picker
|
|
selectedValue={this.state.parsedEmail}
|
|
style={{ height: 50, width: 250 }}
|
|
onValueChange={(itemValue, itemIndex) =>
|
|
this.setState({ parsedEmail: itemValue })
|
|
}>
|
|
<Picker.Item label="" value="0" key={0} />
|
|
{this.state.parsedType === 'vcard' &&
|
|
this.state.parsedDatas.map((item, index) => {
|
|
return (
|
|
<Picker.Item
|
|
label={item}
|
|
value={item}
|
|
key={(index + 1).toString()}
|
|
/>
|
|
);
|
|
})}
|
|
</Picker>
|
|
</View>
|
|
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
|
|
<Text style={{ flex: 1, paddingLeft: 10 }}>
|
|
{i18n.t('global.company')}
|
|
</Text>
|
|
<Picker
|
|
selectedValue={this.state.parsedCompany}
|
|
style={{ height: 50, width: 250 }}
|
|
onValueChange={(itemValue, itemIndex) =>
|
|
this.setState({ parsedCompany: itemValue })
|
|
}>
|
|
<Picker.Item label="" value="0" key={0} />
|
|
{this.state.parsedType === 'vcard' &&
|
|
this.state.parsedDatas.map((item, index) => {
|
|
return (
|
|
<Picker.Item
|
|
label={item}
|
|
value={item}
|
|
key={(index + 1).toString()}
|
|
/>
|
|
);
|
|
})}
|
|
</Picker>
|
|
</View>
|
|
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
|
|
<Text style={{ flex: 1, paddingLeft: 10 }}>
|
|
{i18n.t('global.address')}
|
|
</Text>
|
|
<Picker
|
|
selectedValue={this.state.parsedAddress}
|
|
style={{ height: 50, width: 250 }}
|
|
onValueChange={(itemValue, itemIndex) =>
|
|
this.setState({ parsedAddress: itemValue })
|
|
}>
|
|
<Picker.Item label="" value="0" key={0} />
|
|
{this.state.parsedType === 'vcard' &&
|
|
this.state.parsedDatas.map((item, index) => {
|
|
return (
|
|
<Picker.Item
|
|
label={item}
|
|
value={item}
|
|
key={(index + 1).toString()}
|
|
/>
|
|
);
|
|
})}
|
|
</Picker>
|
|
</View>
|
|
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
|
|
<Text style={{ flex: 1, paddingLeft: 10 }}>
|
|
{i18n.t('global.phone')}
|
|
</Text>
|
|
<Picker
|
|
selectedValue={this.state.parsedPhone}
|
|
style={{ height: 50, width: 250 }}
|
|
onValueChange={(itemValue, itemIndex) =>
|
|
this.setState({ parsedPhone: itemValue })
|
|
}>
|
|
<Picker.Item label="" value="0" key={0} />
|
|
{this.state.parsedType === 'vcard' &&
|
|
this.state.parsedDatas.map((item, index) => {
|
|
return (
|
|
<Picker.Item
|
|
label={item}
|
|
value={item}
|
|
key={(index + 1).toString()}
|
|
/>
|
|
);
|
|
})}
|
|
</Picker>
|
|
</View>
|
|
<View
|
|
style={{
|
|
flexDirection: 'row',
|
|
justifyContent: 'space-evenly',
|
|
alignItems: 'center',
|
|
width: 400,
|
|
}}>
|
|
<Button
|
|
title={i18n.t('global.cancel')}
|
|
onPress={() => {
|
|
this.setState({
|
|
showDynContact: false,
|
|
scanned: false,
|
|
vCard: {
|
|
name: '',
|
|
firstname: '',
|
|
email: '',
|
|
company: '',
|
|
address: '',
|
|
phone: '',
|
|
},
|
|
});
|
|
}}
|
|
color={this.props.mainColor}
|
|
/>
|
|
<Button
|
|
title={i18n.t('global.next')}
|
|
onPress={() => {
|
|
/** @type {typeof import('./../Models/Contact').ContactData} */
|
|
const contact = this.state.contact;
|
|
contact.lastname = this.state.vCardData.name;
|
|
contact.firstname = this.state.vCardData.firstname;
|
|
contact.email = this.state.vCardData.email;
|
|
contact.phone = this.state.vCardData.phone;
|
|
// FIXME: Insert other fields from DATA.
|
|
this.setState({
|
|
contact: contact,
|
|
vCardData: {
|
|
name: '',
|
|
firstname: '',
|
|
email: '',
|
|
company: '',
|
|
address: '',
|
|
phone: '',
|
|
},
|
|
showDynContact: false,
|
|
scanned: false,
|
|
});
|
|
}}
|
|
color={this.props.mainColor}
|
|
/>
|
|
</View>
|
|
</View>
|
|
</Overlay>
|
|
</View>
|
|
);
|
|
}
|
|
|
|
/// * --- RENDER METHODS
|
|
|
|
/**
|
|
* @param {Realm.Results<import('./../Models/Form').Form>} formularies
|
|
* @returns {JSX.Element[]}
|
|
*/
|
|
renderFormulary(formularies) {
|
|
/** @type {JSX.Element} */
|
|
const inputs = [];
|
|
[...formularies.values()]
|
|
.sort((a, b) => a - b)
|
|
.forEach((form, inputIndex) => {
|
|
/** @type {string|null} */
|
|
const placeHolder = form.placeholder[i18n.locale] ?? null;
|
|
const requiredStr = form.required
|
|
? ` (${i18n.t('global.required')})`
|
|
: '';
|
|
/** @type {string[]} Already translated details */
|
|
const details = form.details
|
|
.map(detail => JSON.parse(detail)[i18n.locale] ?? null)
|
|
.filter(detail => detail !== null);
|
|
switch (form.type) {
|
|
case InputType.TEXT:
|
|
inputs.push(
|
|
this.renderTextInput(
|
|
placeHolder,
|
|
requiredStr,
|
|
inputIndex,
|
|
form.slug,
|
|
form.status === InputStatus.DEFAULT,
|
|
'default',
|
|
form.is_visible,
|
|
),
|
|
);
|
|
break;
|
|
case InputType.EMAIL:
|
|
inputs.push(
|
|
this.renderTextInput(
|
|
placeHolder,
|
|
requiredStr,
|
|
inputIndex,
|
|
form.slug,
|
|
form.status === InputStatus.DEFAULT,
|
|
'email-address',
|
|
form.is_visible,
|
|
),
|
|
);
|
|
break;
|
|
case InputType.PHONE:
|
|
inputs.push(
|
|
this.renderTextInput(
|
|
placeHolder,
|
|
requiredStr,
|
|
inputIndex,
|
|
form.slug,
|
|
form.status === InputStatus.DEFAULT,
|
|
'phone-pad',
|
|
form.is_visible,
|
|
),
|
|
);
|
|
break;
|
|
case InputType.CHECKBOX:
|
|
inputs.push(
|
|
this.renderCheckBox(
|
|
placeHolder,
|
|
requiredStr,
|
|
inputIndex,
|
|
form.slug,
|
|
form.status === InputStatus.DEFAULT,
|
|
details,
|
|
form.is_visible,
|
|
),
|
|
);
|
|
break;
|
|
case InputType.RADIO:
|
|
inputs.push(
|
|
this.renderRadio(
|
|
placeHolder,
|
|
requiredStr,
|
|
inputIndex,
|
|
form.slug,
|
|
form.status === InputStatus.DEFAULT,
|
|
details,
|
|
form.is_visible,
|
|
),
|
|
);
|
|
break;
|
|
case InputType.SELECT:
|
|
inputs.push(
|
|
this.renderSelect(
|
|
placeHolder,
|
|
requiredStr,
|
|
inputIndex,
|
|
form.slug,
|
|
form.status === InputStatus.DEFAULT,
|
|
details,
|
|
form.is_visible,
|
|
),
|
|
);
|
|
break;
|
|
case InputType.SELECT_MULTIPLE:
|
|
inputs.push(
|
|
this.renderSelectMultiple(
|
|
placeHolder,
|
|
requiredStr,
|
|
inputIndex,
|
|
form.slug,
|
|
form.status === InputStatus.DEFAULT,
|
|
details,
|
|
form,
|
|
form.is_visible,
|
|
),
|
|
);
|
|
break;
|
|
default:
|
|
throw new Error(
|
|
`Unhandled input type '${form.type}' cannot render input`,
|
|
);
|
|
}
|
|
});
|
|
return inputs;
|
|
}
|
|
|
|
/**
|
|
* Render a TextInput
|
|
*
|
|
* @param {string} placeHolder The place holder text to display
|
|
* @param {string} requiredStr The required text to display
|
|
* @param {number} index The input index (formulary order)
|
|
* @param {string} slug The formulary input name
|
|
* @param {boolean} isDefault The input status is Default ? (if no it is a prop on contact model)
|
|
* @param {string} keyboardType Keyboard type to disaply on user
|
|
* @param {boolean} isVisible The input is Visible
|
|
* @returns
|
|
*/
|
|
renderTextInput(
|
|
placeHolder,
|
|
requiredStr,
|
|
index,
|
|
slug,
|
|
isDefault,
|
|
keyboardType,
|
|
isVisible,
|
|
) {
|
|
/** @type {typeof import('./../Models/Contact').ContactData} */
|
|
const contact = this.state.contact;
|
|
/** @type {Array<import('./../Models/Contact').InputDataType>} */
|
|
const allInputs = this.state.allInputs;
|
|
const changeText = text => {
|
|
if (isDefault) {
|
|
const dataIndex = contact.data.findIndex(
|
|
input => input.formSlug === slug,
|
|
);
|
|
// * Set value on data.
|
|
contact.data[dataIndex].value = text;
|
|
console.debug('data ', contact.data[dataIndex].value);
|
|
} else {
|
|
// * Set value on model
|
|
contact[slug] = text;
|
|
console.debug('prop ', contact[slug]);
|
|
}
|
|
allInputs[index].value = text;
|
|
this.setState({
|
|
contact: contact,
|
|
allInputs: allInputs,
|
|
});
|
|
};
|
|
return this.wrapper(
|
|
placeHolder,
|
|
requiredStr,
|
|
isVisible,
|
|
<TextInput
|
|
value={this.state.allInputs[index].value}
|
|
onEndEditing={event =>
|
|
changeText((event.nativeEvent?.text ?? '').trim())
|
|
}
|
|
onChangeText={changeText}
|
|
style={{ ...styles.input, ...{ display: isVisible ? 'flex' : 'none' } }}
|
|
placeholder={`${placeHolder} ${requiredStr}`}
|
|
autoCapitalize={'none'}
|
|
autoCorrect={false}
|
|
keyboardType={keyboardType}
|
|
/>,
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Render a Checkbox Input (one checkbox per choice)
|
|
*
|
|
* @param {string} placeHolder The place holder text to display
|
|
* @param {string} requiredStr The required text to display
|
|
* @param {number} index The input index (formulary order)
|
|
* @param {string} slug The formulary input name
|
|
* @param {boolean} isDefault The input status is Default ? (if no it is a prop on contact model)
|
|
* @param {string[]} details The details associated with the formulary input
|
|
* @param {boolean} isVisible The input is Visible
|
|
* @returns
|
|
*/
|
|
renderCheckBox(
|
|
placeHolder,
|
|
requiredStr,
|
|
index,
|
|
slug,
|
|
isDefault,
|
|
details,
|
|
isVisible,
|
|
) {
|
|
/** @type {typeof import('./../Models/Contact').ContactData} */
|
|
const contact = this.state.contact;
|
|
/** @type {Array<import('./../Models/Contact').InputDataType>} */
|
|
const allInputs = this.state.allInputs;
|
|
/** @type {string[]} */
|
|
const checkedValues = (allInputs[index].value ?? []).filter(
|
|
checkBoxValue => {
|
|
// * Filter values to remove props that does not exists
|
|
return details.indexOf(checkBoxValue) !== -1;
|
|
},
|
|
);
|
|
|
|
return this.wrapper(
|
|
placeHolder,
|
|
requiredStr,
|
|
isVisible,
|
|
<View
|
|
style={{
|
|
display: isVisible ? 'flex' : 'none',
|
|
flexDirection: 'row',
|
|
flexWrap: 'wrap',
|
|
width: '100%',
|
|
}}>
|
|
{details.map((detail, detailIndex) => {
|
|
/** The index of the checkbox value */
|
|
const indexValue = checkedValues.indexOf(detail);
|
|
return (
|
|
<CheckBox
|
|
checked={
|
|
checkedValues.findIndex(
|
|
checkBoxValue => checkBoxValue === detail,
|
|
) !== -1
|
|
}
|
|
onPress={() => {
|
|
if (indexValue !== -1) {
|
|
checkedValues.splice(indexValue, 1);
|
|
} else {
|
|
checkedValues.push(detail);
|
|
}
|
|
|
|
// * Filter Legacy details to conserve order
|
|
const newCheckedValues = details.filter(
|
|
legacyDetail => checkedValues.indexOf(legacyDetail) !== -1,
|
|
);
|
|
|
|
console.debug(newCheckedValues);
|
|
if (isDefault) {
|
|
const dataIndex = contact.data.findIndex(
|
|
input => input.formSlug === slug,
|
|
);
|
|
// * Set value on data.
|
|
contact.data[dataIndex].value = newCheckedValues;
|
|
console.debug('data ', contact.data[dataIndex]);
|
|
} else {
|
|
// * Set value on model
|
|
contact[slug] = newCheckedValues;
|
|
console.debug('prop ', contact[slug]);
|
|
}
|
|
allInputs[index].value = newCheckedValues;
|
|
|
|
this.setState({
|
|
contact: contact,
|
|
allInputs: allInputs,
|
|
});
|
|
}}
|
|
key={detailIndex}
|
|
checkedColor={this.props.mainColor}
|
|
center
|
|
title={detail}
|
|
containerStyle={{
|
|
maxWidth: 350,
|
|
}}
|
|
/>
|
|
);
|
|
})}
|
|
</View>,
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Render a Select Multiple Input
|
|
*
|
|
* @param {string} placeHolder The place holder text to display
|
|
* @param {string} requiredStr The required text to display
|
|
* @param {number} index The input index (formulary order)
|
|
* @param {string} slug The formulary input name
|
|
* @param {boolean} isDefault The input status is Default ? (if no it is a prop on contact model)
|
|
* @param {string[]} details The details associated with the formulary input
|
|
* @param {import('./../Models/Form').Form} form The Form input
|
|
* @param {boolean} isVisible The input is Visible
|
|
* @returns
|
|
*/
|
|
renderSelectMultiple(
|
|
placeHolder,
|
|
requiredStr,
|
|
index,
|
|
slug,
|
|
isDefault,
|
|
details,
|
|
form,
|
|
isVisible,
|
|
) {
|
|
const multipleSelectInputs = this.state.multipleSelectInputs;
|
|
/** @type {typeof import('./../Models/Contact').ContactData} */
|
|
const contact = this.state.contact;
|
|
|
|
return this.wrapper(
|
|
placeHolder,
|
|
requiredStr,
|
|
isVisible,
|
|
<View
|
|
style={{
|
|
display: isVisible ? 'flex' : 'none',
|
|
flex: 1,
|
|
flexDirection: 'column',
|
|
flexGrow: 1,
|
|
backgroundColor: 'white',
|
|
margin: 10,
|
|
}}>
|
|
<SectionedMultiSelect
|
|
searchPlaceholderText={i18n.t(
|
|
'contact_details_s.input_select_multiple_search',
|
|
)}
|
|
confirmText={i18n.t('global.close')}
|
|
items={details.map(function (detail, detailIndex) {
|
|
return {
|
|
id: detailIndex,
|
|
name: detail,
|
|
};
|
|
})}
|
|
IconRenderer={this.getIcon}
|
|
uniqueKey="id"
|
|
selectText={`${placeHolder} ${requiredStr}`}
|
|
showDropDowns={false}
|
|
showChips={false}
|
|
modalWithTouchable={true}
|
|
alwaysShowSelectText={true}
|
|
renderSelectText={props =>
|
|
props.selectedItems
|
|
.map(detailIndex => details[detailIndex])
|
|
.join(', ')
|
|
}
|
|
onSelectedItemsChange={items => {
|
|
/** @var number[] */
|
|
const selectedItems = items;
|
|
// * Filter Legacy details to conserve order
|
|
const selectedItemsLabels = details.filter(
|
|
(legacyDetail, legacyDetailIndex) =>
|
|
selectedItems.indexOf(legacyDetailIndex) !== -1,
|
|
);
|
|
// * Update Selected State
|
|
multipleSelectInputs[`input${form.id}`].selectedItems =
|
|
selectedItems;
|
|
|
|
console.debug('IS DEfault', isDefault ? 'true' : 'false');
|
|
console.debug('selected Items', selectedItems);
|
|
console.debug('selected Items labels', selectedItemsLabels);
|
|
|
|
if (isDefault) {
|
|
const dataIndex = contact.data.findIndex(
|
|
input => input.formSlug === slug,
|
|
);
|
|
// * Set value on data.
|
|
contact.data[dataIndex].value = selectedItemsLabels;
|
|
console.debug('data ', contact.data[dataIndex].value);
|
|
} else {
|
|
// * Set value on model
|
|
contact[slug] = selectedItemsLabels;
|
|
console.debug('prop ', contact[slug]);
|
|
}
|
|
|
|
this.setState({
|
|
multipleSelectInputs: multipleSelectInputs,
|
|
contact: contact,
|
|
});
|
|
}}
|
|
selectedItems={multipleSelectInputs[`input${form.id}`].selectedItems}
|
|
icons={{
|
|
search: {
|
|
name: 'search',
|
|
size: 24,
|
|
style: {
|
|
fa: 'search',
|
|
left: 10,
|
|
},
|
|
},
|
|
arrowUp: {
|
|
name: 'keyboard-arrow-up',
|
|
size: 22,
|
|
style: {
|
|
fa: 'sort-up',
|
|
},
|
|
},
|
|
arrowDown: {
|
|
name: 'keyboard-arrow-down',
|
|
size: 22,
|
|
style: {
|
|
fa: 'sort-down',
|
|
},
|
|
},
|
|
selectArrowDown: {
|
|
name: 'keyboard-arrow-down',
|
|
size: 20,
|
|
style: {
|
|
fa: 'sort-down',
|
|
right: 20,
|
|
},
|
|
},
|
|
close: {
|
|
name: 'close',
|
|
size: 16,
|
|
style: {
|
|
fa: 'window-close',
|
|
},
|
|
},
|
|
check: {
|
|
name: 'check-circle',
|
|
size: 24,
|
|
style: {
|
|
fa: 'check-circle',
|
|
position: 'absolute',
|
|
left: 0,
|
|
top: 20,
|
|
},
|
|
},
|
|
cancel: {
|
|
name: 'cancel',
|
|
size: 18,
|
|
style: {
|
|
fa: 'times',
|
|
},
|
|
},
|
|
}}
|
|
styles={{
|
|
button: {
|
|
backgroundColor: this.props.mainColor,
|
|
maxWidth: 150,
|
|
margin: 20,
|
|
left: '80%',
|
|
},
|
|
searchTextInput: {
|
|
paddingLeft: 20,
|
|
},
|
|
container: {
|
|
paddingTop: 20,
|
|
width: '90%',
|
|
height: '60%',
|
|
flex: 0,
|
|
alignSelf: 'center',
|
|
justifyContent: 'center',
|
|
top: '10%',
|
|
},
|
|
selectToggle: {
|
|
marginTop: 7,
|
|
height: 50,
|
|
},
|
|
selectToggleText: {
|
|
fontSize: 16,
|
|
marginStart: 13,
|
|
marginTop: 15,
|
|
},
|
|
separator: {
|
|
color: 'white',
|
|
borderWidth: 0,
|
|
display: 'none',
|
|
},
|
|
item: {
|
|
paddingTop: 20,
|
|
paddingBottom: 20,
|
|
width: '100%',
|
|
},
|
|
selectedItem: {
|
|
paddingTop: 20,
|
|
paddingBottom: 20,
|
|
},
|
|
selectedItemText: {
|
|
fontStyle: 'italic',
|
|
left: 40,
|
|
},
|
|
}}
|
|
/>
|
|
</View>,
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Render a Select Input
|
|
*
|
|
* @param {string} placeHolder The place holder text to display
|
|
* @param {string} requiredStr The required text to display
|
|
* @param {number} index The input index (formulary order)
|
|
* @param {string} slug The formulary input name
|
|
* @param {boolean} isDefault The input status is Default ? (if no it is a prop on contact model)
|
|
* @param {string[]} details The details associated with the formulary input
|
|
* @param {boolean} isVisible The input is Visible
|
|
* @returns
|
|
*/
|
|
renderSelect(
|
|
placeHolder,
|
|
requiredStr,
|
|
index,
|
|
slug,
|
|
isDefault,
|
|
details,
|
|
isVisible,
|
|
) {
|
|
/** String separator for input value */
|
|
// const separator = ', ';
|
|
/** @type {typeof import('./../Models/Contact').ContactData} */
|
|
const contact = this.state.contact;
|
|
/** @type {Array<import('./../Models/Contact').InputDataType>} */
|
|
const allInputs = this.state.allInputs;
|
|
const selected = allInputs[index].value ?? '';
|
|
// * Make sure the actual option is available
|
|
const filteredSelected =
|
|
details.findIndex(legacyDetail => legacyDetail === selected) ?? -1;
|
|
|
|
return this.wrapper(
|
|
placeHolder,
|
|
requiredStr,
|
|
isVisible,
|
|
<View
|
|
style={{
|
|
display: isVisible ? 'flex' : 'none',
|
|
flex: 1,
|
|
flexDirection: 'row',
|
|
}}>
|
|
<FontAwesome5
|
|
name="sort-down"
|
|
size={20}
|
|
color={'black'}
|
|
style={{
|
|
zIndex: 1,
|
|
position: 'absolute',
|
|
right: 28,
|
|
top: 22,
|
|
}}
|
|
/>
|
|
<Picker
|
|
selectedValue={filteredSelected}
|
|
onValueChange={itemIndex => {
|
|
if (isDefault) {
|
|
const dataIndex = contact.data.findIndex(
|
|
input => input.formSlug === slug,
|
|
);
|
|
console.debug(dataIndex);
|
|
console.debug(contact.data);
|
|
// * Set value on data.
|
|
contact.data[dataIndex].value = details[itemIndex] ?? undefined;
|
|
console.debug(contact.data[dataIndex]);
|
|
} else {
|
|
// * Set value on model
|
|
contact[slug] = details[itemIndex] ?? undefined;
|
|
console.debug(contact[slug]);
|
|
}
|
|
allInputs[index].value = details[itemIndex] ?? undefined;
|
|
this.setState({
|
|
contact: contact,
|
|
allInputs: allInputs,
|
|
});
|
|
}}
|
|
style={styles.select}>
|
|
<Picker.Item
|
|
key={-1}
|
|
label={i18n.t('contact_details_s.input_select_please_choose')}
|
|
value={''}
|
|
/>
|
|
{details.map((detail, detailIndex) => (
|
|
<Picker.Item key={detailIndex} label={detail} value={detailIndex} />
|
|
))}
|
|
</Picker>
|
|
</View>,
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Render a Radio Input
|
|
*
|
|
* @param {string} placeHolder The place holder text to display
|
|
* @param {string} requiredStr The required text to display
|
|
* @param {number} index The input index (formulary order)
|
|
* @param {string} slug The formulary input name
|
|
* @param {boolean} isDefault The input status is Default ? (if no it is a prop on contact model)
|
|
* @param {string[]} details The details associated with the formulary input
|
|
* @param {boolean} isVisible The input is Visible
|
|
* @returns
|
|
*/
|
|
renderRadio(
|
|
placeHolder,
|
|
requiredStr,
|
|
index,
|
|
slug,
|
|
isDefault,
|
|
details,
|
|
isVisible,
|
|
) {
|
|
/** String separator for input value */
|
|
// const separator = ', ';
|
|
/** @type {typeof import('./../Models/Contact').ContactData} */
|
|
const contact = this.state.contact;
|
|
/** @type {Array<import('./../Models/Contact').InputDataType>} */
|
|
const allInputs = this.state.allInputs;
|
|
const selected = String(this.state.allInputs[index].value) ?? '';
|
|
// * Make sure the actual option is available
|
|
const filteredSelected =
|
|
details.findIndex(legacyDetail => legacyDetail === selected) ?? -1;
|
|
|
|
return this.wrapper(
|
|
placeHolder,
|
|
requiredStr,
|
|
isVisible,
|
|
<View
|
|
style={{
|
|
display: isVisible ? 'flex' : 'none',
|
|
flexDirection: 'row',
|
|
flexWrap: 'wrap',
|
|
width: '100%',
|
|
}}>
|
|
{details.map((detail, detailIndex) => (
|
|
<CheckBox
|
|
checked={filteredSelected === detailIndex}
|
|
onPress={() => {
|
|
if (isDefault) {
|
|
const dataIndex = contact.data.findIndex(
|
|
input => input.formSlug === slug,
|
|
);
|
|
// * Set value on data.
|
|
contact.data[dataIndex].value = detail;
|
|
console.debug(contact.data[dataIndex]);
|
|
} else {
|
|
// * Set value on model
|
|
contact[slug] = detail;
|
|
console.debug(contact[slug]);
|
|
}
|
|
allInputs[index].value = detail;
|
|
this.setState({
|
|
contact: contact,
|
|
allInputs: allInputs,
|
|
});
|
|
}}
|
|
key={detailIndex}
|
|
checkedColor={this.props.mainColor}
|
|
center
|
|
title={detail}
|
|
checkedIcon="dot-circle-o"
|
|
uncheckedIcon="circle-o"
|
|
containerStyle={
|
|
(styles.clearRadio,
|
|
{
|
|
maxWidth: 350,
|
|
})
|
|
}
|
|
/>
|
|
))}
|
|
</View>,
|
|
);
|
|
}
|
|
|
|
/**
|
|
* wrap input
|
|
* @param {string} placeHolder
|
|
* @param {string} requiredStr
|
|
* @param {boolean} isVisible
|
|
* @param {JSX.Element} el
|
|
* @returns {JSX.Element}
|
|
*/
|
|
wrapper(placeHolder, requiredStr, isVisible, el) {
|
|
return (
|
|
<View
|
|
style={{
|
|
display: isVisible ? 'flex' : 'none',
|
|
flex: 1,
|
|
flexDirection: 'column',
|
|
}}>
|
|
<Text
|
|
style={
|
|
(styles.baseText,
|
|
{ paddingTop: 25, paddingStart: 10, fontSize: 16 })
|
|
}>{`${placeHolder} ${requiredStr}`}</Text>
|
|
{el}
|
|
</View>
|
|
);
|
|
}
|
|
|
|
/** Icon Builder for Select Multi */
|
|
getIcon({ name, size = 18, style }) {
|
|
// flatten the styles
|
|
const flat = StyleSheet.flatten(style);
|
|
// remove out the keys that aren't accepted on View
|
|
const { color, fontSize, fa, ...styles } = flat;
|
|
|
|
return name ? (
|
|
<FontAwesome5 name={fa} size={size} color={color} style={styles} />
|
|
) : (
|
|
<View />
|
|
);
|
|
}
|
|
|
|
/// * END --- RENDER METHODS
|
|
}
|
|
|
|
export default connect(
|
|
mapStateToProps,
|
|
mapDispatchToProps,
|
|
)(ContactDetailsScreen);
|