ldc-com-app/ComApp/src/App/Views/ContactScreen.js

744 lines
24 KiB
JavaScript

import React from 'react';
import {
Alert,
Button,
FlatList,
Linking,
Platform,
Text,
TextInput,
TouchableOpacity,
View,
} from 'react-native';
import { openComposer } from 'react-native-email-link';
// Reduxs
import { connect } from 'react-redux';
import { deleteContact, 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';
// Flash Messages
import { showMessage } from 'react-native-flash-message';
// Overlay
import { Overlay } from '@rneui/themed';
// Picker
import { Picker } from '@react-native-picker/picker';
// Style
import styles from '../../Resources/Styles/ContactStyle';
function mapStateToProps(state) {
return {
users: state.users.users,
contacts: state.contacts,
documents: state.documents,
currentUser: state.app.currentUser,
mainColor: state.app.mainColor,
};
}
/**
* @typedef {(user: import('./../Models/User').User, data: import('./../Models/Contact').Contact) => void} setContact
* @typedef {(user: import('./../Models/User').User, uuid: string ) => void} deleteContact
* @typedef {(email: string ) => void} sendBrochure
*/
const mapDispatchToProps = dispatch => {
return {
setContact: (user, data) => dispatch(setContact(user, data)),
deleteContact: (user, id) => dispatch(deleteContact(user, id)),
sendBrochure: email => dispatch({ type: 'SEND_BROCHURE', data: email }),
};
};
class ContactScreen extends React.Component {
state = {
contacts: [],
sortType: 'asc',
sortField: 'name',
searchTxt: '',
showSendBrochure: false,
selectedDocument: -1,
sendDocumentList: [],
selectedSendBrochureEmail: -1,
tmpSendBrochureEmail: '',
tmpSendBrochureSubject: '',
tmpSendBrochureMessage: '',
sortedDocuments: [],
sortedEmails: [],
/** @type {number} The user index selected for transfert */
selectedTransferUserIndex: 0,
/** @type {import('./../Models/Contact').Contact|null} The contact to transfert */
contactToTransfer: null,
/** @type {boolean} Is tranfert contact model display ? */
showTransferContact: false,
};
// * --- ACTIONS
sendBrochure() {
// FIXME: Si on n'a pas consulté les documents la liste des documents est vide apres initialisation
if (
this.state.tmpSendBrochureEmail !== '' &&
this.state.tmpSendBrochureSubject !== '' &&
this.state.tmpSendBrochureMessage !== '' &&
this.state.sendDocumentList.length > 0 &&
Helpers.isEmailValid(this.state.tmpSendBrochureEmail)
) {
/** @type {sendBrochure} */
const sendBrochure = this.props.sendBrochure;
const email = {
email: this.state.tmpSendBrochureEmail,
subject: this.state.tmpSendBrochureSubject,
message: this.state.tmpSendBrochureMessage,
documents: JSON.stringify(this.state.sendDocumentList),
app_user_id: this.props.currentUser.id,
};
sendBrochure(email);
this.setState({
showSendBrochure: false,
tmpSendBrochureEmail: '',
tmpSendBrochureSubject: '',
tmpSendBrochureMessage: '',
selectedSendBrochureEmail: -1,
selectedDocument: -1,
sendDocumentList: [],
});
showMessage({
message: i18n.t('global.message_sent') + '.',
description: i18n.t('global.message_will_be_sent_after_sync') + '.',
type: 'success',
duration: 1500,
});
} else {
Alert.alert(
i18n.t('error.incomplete_form'),
i18n.t('error.fill_all_fields_and_1_doc_min') + '.',
);
}
}
transferContact() {
/** @type {import('./../Models/User').User} */
const nextUser = this.props.users.filter(
user => user.id !== this.props.currentUser.id,
)[this.state.selectedTransferUserIndex];
/** @type {import('./../Models/Contact').ContactData} cloned contact */
const transferContact = this.state.contactToTransfer.toJSON();
if (transferContact === null) {
throw new Error('No selected contact to transfert');
}
// * Assign cloned contact to another user
transferContact.send_to_app_user_id = nextUser.id;
transferContact.timestamp = new Date();
transferContact.synced = false;
/** @type {setContact} */
const setContactHook = this.props.setContact;
setContactHook(this.props.currentUser, transferContact);
this.setState({ showTransferContact: false, contactToTransfer: null });
showMessage({
message: i18n.t('contact_s.contact_transferred') + '.',
description: i18n.t('contact_s.contact_will_be_sent_after_sync') + '.',
type: 'success',
duration: 1500,
});
}
/**
* Delete contact
* @param {import('../Models/Contact').Contact} contact
*/
deleteContactAction(contact) {
Alert.alert(
i18n.t('global.confirmation'),
i18n.t('contact_s.confirm_contact_delete'),
[
{
text: i18n.t('global.no'),
onPress: () => console.debug('Cancel Pressed'),
style: 'cancel',
},
{
text: i18n.t('global.yes'),
onPress: () => {
this.setState({ contacts: [], sortedEmails: [] });
/** @type {setContact} */
const setContactHook = this.props.setContact;
/** @type {import('./../Models/Contact').ContactData} */
const contactToDelete = contact.toJSON();
contactToDelete.to_delete = true;
setContactHook(this.props.currentUser, contactToDelete);
showMessage({
message: i18n.t('contact_s.contact_deleted') + '.',
type: 'success',
duration: 500,
});
},
},
],
{ cancelable: false },
);
}
/**
* Try to call the number
*
* @param {string} phone
*/
callNumberIfSupported(phone) {
let phoneNumber = phone;
if (Platform.OS !== 'android') {
phoneNumber = `telprompt:${phone}`;
} else {
phoneNumber = `tel:${phone}`;
}
Linking.canOpenURL(phoneNumber)
.then(supported => {
if (supported) {
return Linking.openURL(phoneNumber);
}
})
.catch(err => console.error(err));
}
/**
* Write a new email to
*
* @param {string} email
*/
composeNewEmailTo(email) {
openComposer({
to: email,
});
}
// * END --- ACTIONS
/**
* Render the table header
* @returns {JSX.Element}
*/
HeaderItem() {
return (
<View style={styles.item}>
<TouchableOpacity
onPress={() => {
this.sortContacts('lastname');
}}>
<Text style={{ ...styles.clickable, color: this.props.mainColor }}>
{i18n.t('global.lastname')} <FontAwesome5 name={'sort'} />
</Text>
</TouchableOpacity>
<TouchableOpacity
onPress={() => {
this.sortContacts('email');
}}>
<Text style={{ ...styles.clickable, color: this.props.mainColor }}>
{i18n.t('global.email')} <FontAwesome5 name={'sort'} />
</Text>
</TouchableOpacity>
<TouchableOpacity
onPress={() => {
this.sortContacts('phone');
}}>
<Text style={{ ...styles.clickable, color: this.props.mainColor }}>
{i18n.t('global.phone')} <FontAwesome5 name={'sort'} />
</Text>
</TouchableOpacity>
<Text style={styles.buttons} />
</View>
);
}
/**
* Render a line of contact
* @param {import('../Models/Contact').Contact} contact
* @returns
*/
Item(contact) {
const displayName =
String(contact.lastname ?? '').toUpperCase() +
' ' +
(contact.firstname ?? '');
return (
<View style={styles.item}>
<TouchableOpacity
onPress={() =>
this.props.navigation.navigate('ContactDetailsScreen', {
action: 'edit',
contactId: contact.id,
})
}>
<Text style={{ ...styles.clickable, color: this.props.mainColor }}>
{contact.lastname || contact.firstname ? displayName : contact.id}
</Text>
</TouchableOpacity>
<TouchableOpacity onPress={() => this.composeNewEmailTo(contact.email)}>
<Text style={{ ...styles.clickable }}>{contact.email}</Text>
</TouchableOpacity>
<TouchableOpacity
onPress={() =>
contact.phone ? this.callNumberIfSupported(contact.phone) : false
}>
<Text style={{ ...styles.clickable }}>{contact.phone}</Text>
</TouchableOpacity>
<View style={styles.buttons}>
<TouchableOpacity
style={styles.button}
onPress={() => {
this.deleteContactAction(contact);
}}>
<FontAwesome5 name="trash" size={20} color={this.props.mainColor} />
</TouchableOpacity>
<TouchableOpacity
style={styles.button}
onPress={() => {
this.setState({
showTransferContact: true,
contactToTransfer: contact,
});
}}>
<FontAwesome5
name="paper-plane"
size={20}
color={this.props.mainColor}
/>
</TouchableOpacity>
</View>
</View>
);
}
sortContacts(field) {
let sort;
if (field !== this.state.sortField) {
sort = 'asc';
} else {
if (this.state.sortType === 'asc') {
sort = 'desc';
} else {
sort = 'asc';
}
}
this.setState({
contacts: this.state.contacts.sort(Helpers.compareValues(field, sort)),
sortType: sort,
sortField: field,
});
}
handleSearchBtn() {
if (this.state.searchTxt.length > 0) {
let obj = this.props.contacts;
let array = Object.keys(obj).map(function (key) {
return obj[key];
});
this.setState({
contacts: array.sort(Helpers.compareValues('name', 'asc')),
sortType: 'asc',
searchTxt: '',
});
}
}
handleSearchTxt(text) {
this.setState({ searchTxt: text });
/** @type {Array<import('./../Models/Contact').Contact>} */
const contacts = this.props.contacts;
if (text.length > 0) {
const tmpContactsLastname = contacts.filter(
item => item.lastname?.toLowerCase()?.indexOf(text.toLowerCase()) >= 0,
);
const tmpContactsFirstname = contacts.filter(
item => item.firstname?.toLowerCase()?.indexOf(text.toLowerCase()) >= 0,
);
const tmpContactsEmail = contacts.filter(
item => item.email.toLowerCase().indexOf(text.toLowerCase()) >= 0,
);
const tmpContactsPhone = contacts.filter(
item => item.phone?.toLowerCase()?.indexOf(text.toLowerCase()) >= 0,
);
const resSearch = new Map();
tmpContactsLastname.forEach(contact =>
resSearch.set(contact.id, contact),
);
tmpContactsFirstname.forEach(contact =>
resSearch.set(contact.id, contact),
);
tmpContactsEmail.forEach(contact => resSearch.set(contact.id, contact));
tmpContactsPhone.forEach(contact => resSearch.set(contact.id, contact));
this.setState({
contacts: [...resSearch.values()].sort(
Helpers.compareValues('name', 'asc'),
),
sortType: 'asc',
});
} else {
let obj = this.props.contacts;
let array = Object.keys(obj).map(function (key) {
return obj[key];
});
this.setState({
contacts: array.sort(Helpers.compareValues('name', 'asc')),
sortType: 'asc',
});
}
}
componentDidMount() {
this.init();
}
componentDidUpdate(prevProps, prevState, snapshot) {
if (prevProps.contacts !== this.props.contacts) {
this.init();
}
}
init() {
let objContacts = this.props.contacts;
let objDocs = this.props.documents;
let arrayContacts = Object.keys(objContacts).map(function (key) {
return objContacts[key];
});
let arrayEmails = Object.keys(objContacts).map(function (key) {
return objContacts[key];
});
let arrayDocs = Object.keys(objDocs).map(function (key) {
return objDocs[key];
});
const { action } = this.props.route.params;
if (action === 'add') {
this.props.navigation.navigate('ContactDetailsScreen', {
action: 'add',
});
}
this.setState({
contacts: arrayContacts.sort(Helpers.compareValues('timestamp', 'desc')),
sortedDocuments: arrayDocs.sort(Helpers.compareValues('title', 'asc')),
sortedEmails: arrayEmails.sort(Helpers.compareValues('email', 'asc')),
sortType: 'asc',
sortField: 'name',
});
}
render() {
const { contactAdded } = this.props.route.params;
if (contactAdded === true) {
this.props.navigation.setParams({ contactAdded: false });
showMessage({
message: i18n.t('contact_s.contact_added') + '.',
type: 'success',
duration: 500,
});
}
return (
<View style={styles.container}>
{/* ACTIONS */}
<View
style={{
marginTop: 20,
flexDirection: 'row',
justifyContent: 'space-evenly',
alignItems: 'center',
}}>
<Button
color={this.props.mainColor}
title={i18n.t('global.add')}
onPress={() =>
this.props.navigation.navigate('ContactDetailsScreen', {
action: 'add',
})
}
/>
<Button
color={this.props.mainColor}
title={i18n.t('contact_s.send_flyer')}
onPress={() => this.setState({ showSendBrochure: true })}
/>
<View style={{ flexDirection: 'row' }}>
<TextInput
placeholder={i18n.t('global.search')}
style={styles.inputSearch}
value={this.state.searchTxt}
onChangeText={text => this.handleSearchTxt(text)}
/>
<TouchableOpacity
style={{
...styles.btnSearch,
backgroundColor: this.props.mainColor,
}}
onPress={() => this.handleSearchBtn()}>
{this.state.searchTxt.length > 0 ? (
<FontAwesome5 name={'times'} size={20} color={'white'} />
) : (
<FontAwesome5 name={'search'} size={20} color={'white'} />
)}
</TouchableOpacity>
</View>
</View>
{/* TABLE view */}
<View style={{ flex: 1, marginTop: 20 }}>
<FlatList
data={this.state.contacts}
renderItem={({ item }) => this.Item(item)}
ListHeaderComponent={this.HeaderItem.bind(this)}
keyExtractor={item => item.id.toString()}
/>
</View>
{/* TRANSFERT CONTACT POPUP */}
<Overlay
isVisible={this.state.showTransferContact}
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_s.select_contact')} :
</Text>
<Picker
style={{
width: 300,
marginBottom: 15,
paddingLeft: 10,
}}
selectedValue={this.state.selectedTransferUserIndex}
onValueChange={(itemValue, itemIndex) => {
this.setState({ selectedTransferUserIndex: itemIndex });
}}>
{this.props.users
.filter(user => user.id !== this.props.currentUser.id)
.sort(Helpers.compareValues('email', 'asc'))
.map((item, index) => {
return (
<Picker.Item
label={item.email}
value={index}
key={item.id.toString()}
/>
);
})}
</Picker>
<View
style={{
flexDirection: 'row',
justifyContent: 'space-evenly',
alignItems: 'center',
width: 400,
}}>
<Button
color={this.props.mainColor}
title={i18n.t('global.cancel')}
onPress={() => this.setState({ showTransferContact: false })}
/>
<Button
color={this.props.mainColor}
title={i18n.t('contact_s.transfer')}
onPress={() => this.transferContact()}
/>
</View>
</View>
</Overlay>
{/* DELETE CONTACT POPUP */}
<Overlay
isVisible={this.state.showSendBrochure}
windowBackgroundColor="rgba(255, 255, 255, .9)"
overlayBackgroundColor="white"
width={600}
height={500}>
<View style={{ flex: 1 }}>
<View style={{ flex: 2, flexDirection: 'row' }}>
<View
style={{
flex: 1,
alignItems: 'center',
justifyContent: 'flex-start',
}}>
<TextInput
style={styles.txtSendBrochure}
placeholder={i18n.t('global.email')}
autoCompleteType="email"
autoCapitalize="none"
keyboardType={'email-address'}
value={this.state.tmpSendBrochureEmail}
onChangeText={text =>
this.setState({ tmpSendBrochureEmail: text })
}
/>
<Picker
style={{
width: 300,
marginBottom: 15,
paddingLeft: 10,
}}
selectedValue={this.state.selectedSendBrochureEmail}
onValueChange={(itemValue, itemIndex) => {
if (itemValue !== '-1') {
this.setState({
tmpSendBrochureEmail:
this.state.sortedEmails[itemIndex - 1].email,
});
}
}}>
<Picker.Item
label={i18n.t('contact_s.contact_list') + ' :'}
value="-1"
/>
{this.state.sortedEmails.map((item, index) => {
return (
<Picker.Item
label={item.email}
value={index}
key={item.id.toString()}
/>
);
})}
</Picker>
<TextInput
style={styles.txtSendBrochure}
placeholder={i18n.t('global.subject')}
value={this.state.tmpSendBrochureSubject}
onChangeText={text =>
this.setState({ tmpSendBrochureSubject: text })
}
/>
<TextInput
style={styles.textAreaSendBrochure}
placeholder={i18n.t('global.message')}
value={this.state.tmpSendBrochureMessage}
multiline={true}
underlineColorAndroid="transparent"
onChangeText={text =>
this.setState({ tmpSendBrochureMessage: text })
}
/>
<Picker
style={{
width: 300,
marginBottom: 15,
paddingLeft: 10,
}}
selectedValue={this.state.selectedDocument}
onValueChange={(itemValue, itemIndex) => {
if (
itemValue !== '-1' &&
this.state.sendDocumentList.length < 8
) {
const tmp = this.state.sendDocumentList;
const toPush = this.state.sortedDocuments[itemIndex - 1];
const test = this.state.sendDocumentList.filter(
item => item.id === toPush.id,
);
if (test.length === 0) {
tmp.push(toPush);
this.setState({
sendDocumentList: tmp,
});
}
}
}}>
<Picker.Item
label={i18n.t('contact_s.documents_list') + ' :'}
value="-1"
/>
{this.state.sortedDocuments.map((item, index) => {
return (
<Picker.Item
label={
item.title + ' (' + item.type.toUpperCase() + ')'
}
value={index}
key={item.id.toString()}
/>
);
})}
</Picker>
</View>
<View
style={{
flex: 1,
borderWidth: 1,
borderColor: 'lightgrey',
alignItems: 'center',
justifyContent: 'center',
width: 600,
}}>
{this.state.sendDocumentList.map((item, index) => {
return (
<TouchableOpacity
key={index}
onPress={() => {
const tmp = this.state.sendDocumentList;
tmp.splice(this.props.documents[index], 1);
this.setState({
sendDocumentList: tmp,
});
}}>
<View
style={{
borderWidth: 1,
borderColor: 'lightgrey',
padding: 5,
margin: 5,
borderRadius: 5,
}}>
<Text>
{item.title} ({item.type.toUpperCase()}){' '}
<FontAwesome5 name="minus" size={15} />
</Text>
</View>
</TouchableOpacity>
);
})}
</View>
</View>
<View
style={{
flexDirection: 'row',
justifyContent: 'space-evenly',
alignItems: 'center',
width: 600,
marginTop: 15,
}}>
<Button
color={this.props.mainColor}
title={i18n.t('global.cancel')}
onPress={() =>
this.setState({
showSendBrochure: false,
tmpSendBrochureEmail: '',
tmpSendBrochureSubject: '',
tmpSendBrochureMessage: '',
selectedSendBrochureEmail: -1,
selectedDocument: -1,
sendDocumentList: [],
})
}
/>
<Button
title={i18n.t('global.send')}
onPress={() => this.sendBrochure()}
color={this.props.mainColor}
/>
</View>
</View>
</Overlay>
</View>
);
}
}
export default connect(mapStateToProps, mapDispatchToProps)(ContactScreen);