diff --git a/ComApp/android/app/src/main/res/drawable/launch_screen.png b/ComApp/android/app/src/main/res/drawable/launch_screen.png new file mode 100644 index 0000000..9449be2 Binary files /dev/null and b/ComApp/android/app/src/main/res/drawable/launch_screen.png differ diff --git a/ComApp/android/app/src/main/res/layout/launch_screen.xml b/ComApp/android/app/src/main/res/layout/launch_screen.xml new file mode 100644 index 0000000..323780c --- /dev/null +++ b/ComApp/android/app/src/main/res/layout/launch_screen.xml @@ -0,0 +1,14 @@ + + + + + diff --git a/ComApp/android/app/src/main/res/values/colors.xml b/ComApp/android/app/src/main/res/values/colors.xml new file mode 100644 index 0000000..97d7b59 --- /dev/null +++ b/ComApp/android/app/src/main/res/values/colors.xml @@ -0,0 +1,5 @@ + + + #000000 + #FFFFFF + diff --git a/ComApp/android/app/src/main/res/values/ic_launcher_background.xml b/ComApp/android/app/src/main/res/values/ic_launcher_background.xml new file mode 100644 index 0000000..cfa9be0 --- /dev/null +++ b/ComApp/android/app/src/main/res/values/ic_launcher_background.xml @@ -0,0 +1,4 @@ + + + #FFFFFF + \ No newline at end of file diff --git a/ComApp/package-lock.json b/ComApp/package-lock.json index a6d22cf..b8fa537 100644 --- a/ComApp/package-lock.json +++ b/ComApp/package-lock.json @@ -30,6 +30,7 @@ "react-native-b-crypt-bridge": "file:react-native-b-crypt-bridge-2.0.0.tgz", "react-native-blob-util": "^0.16.2", "react-native-flash-message": "^0.3.1", + "react-native-gesture-handler": "^2.7.0", "react-native-pdf": "^6.6.2", "react-native-progress": "^5.0.0", "react-native-safe-area-context": "^4.4.1", @@ -2014,6 +2015,17 @@ "node": ">=0.1.95" } }, + "node_modules/@egjs/hammerjs": { + "version": "2.0.17", + "resolved": "https://registry.npmjs.org/@egjs/hammerjs/-/hammerjs-2.0.17.tgz", + "integrity": "sha512-XQsZgjm2EcVUiZQf11UBJQfmZeEmOW8DpI1gsFeln6w0ae0ii4dMQEQ0kjl6DspdWX1aGY1/loyXnP0JS06e/A==", + "dependencies": { + "@types/hammerjs": "^2.0.36" + }, + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/@eslint/eslintrc": { "version": "0.4.3", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.3.tgz", @@ -4390,6 +4402,11 @@ "@types/node": "*" } }, + "node_modules/@types/hammerjs": { + "version": "2.0.41", + "resolved": "https://registry.npmjs.org/@types/hammerjs/-/hammerjs-2.0.41.tgz", + "integrity": "sha512-ewXv/ceBaJprikMcxCmWU1FKyMAQ2X7a9Gtmzw8fcg2kIePI1crERDM818W+XYrxqdBBOdlf2rm137bU+BltCA==" + }, "node_modules/@types/hoist-non-react-statics": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz", @@ -16870,6 +16887,22 @@ "react-native": "*" } }, + "node_modules/react-native-gesture-handler": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/react-native-gesture-handler/-/react-native-gesture-handler-2.7.0.tgz", + "integrity": "sha512-0jr3FNm2R3gv/v6XTtENgjv0fewD6LEct8EWmXw/oHw36M3YiIIpxnW57thL+0YiKwyLBXN0QHL4JZbs/heW2Q==", + "dependencies": { + "@egjs/hammerjs": "^2.0.17", + "hoist-non-react-statics": "^3.3.0", + "invariant": "^2.2.4", + "lodash": "^4.17.21", + "prop-types": "^15.7.2" + }, + "peerDependencies": { + "react": "*", + "react-native": "*" + } + }, "node_modules/react-native-gradle-plugin": { "version": "0.70.3", "resolved": "https://registry.npmjs.org/react-native-gradle-plugin/-/react-native-gradle-plugin-0.70.3.tgz", @@ -21586,6 +21619,14 @@ "minimist": "^1.2.0" } }, + "@egjs/hammerjs": { + "version": "2.0.17", + "resolved": "https://registry.npmjs.org/@egjs/hammerjs/-/hammerjs-2.0.17.tgz", + "integrity": "sha512-XQsZgjm2EcVUiZQf11UBJQfmZeEmOW8DpI1gsFeln6w0ae0ii4dMQEQ0kjl6DspdWX1aGY1/loyXnP0JS06e/A==", + "requires": { + "@types/hammerjs": "^2.0.36" + } + }, "@eslint/eslintrc": { "version": "0.4.3", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.3.tgz", @@ -23457,6 +23498,11 @@ "@types/node": "*" } }, + "@types/hammerjs": { + "version": "2.0.41", + "resolved": "https://registry.npmjs.org/@types/hammerjs/-/hammerjs-2.0.41.tgz", + "integrity": "sha512-ewXv/ceBaJprikMcxCmWU1FKyMAQ2X7a9Gtmzw8fcg2kIePI1crERDM818W+XYrxqdBBOdlf2rm137bU+BltCA==" + }, "@types/hoist-non-react-statics": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz", @@ -32440,6 +32486,18 @@ "react-native-iphone-x-helper": "^1.3.1" } }, + "react-native-gesture-handler": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/react-native-gesture-handler/-/react-native-gesture-handler-2.7.0.tgz", + "integrity": "sha512-0jr3FNm2R3gv/v6XTtENgjv0fewD6LEct8EWmXw/oHw36M3YiIIpxnW57thL+0YiKwyLBXN0QHL4JZbs/heW2Q==", + "requires": { + "@egjs/hammerjs": "^2.0.17", + "hoist-non-react-statics": "^3.3.0", + "invariant": "^2.2.4", + "lodash": "^4.17.21", + "prop-types": "^15.7.2" + } + }, "react-native-gradle-plugin": { "version": "0.70.3", "resolved": "https://registry.npmjs.org/react-native-gradle-plugin/-/react-native-gradle-plugin-0.70.3.tgz", diff --git a/ComApp/package.json b/ComApp/package.json index bc5c2a6..0298301 100644 --- a/ComApp/package.json +++ b/ComApp/package.json @@ -32,6 +32,7 @@ "react-native-b-crypt-bridge": "file:react-native-b-crypt-bridge-2.0.0.tgz", "react-native-blob-util": "^0.16.2", "react-native-flash-message": "^0.3.1", + "react-native-gesture-handler": "^2.7.0", "react-native-pdf": "^6.6.2", "react-native-progress": "^5.0.0", "react-native-safe-area-context": "^4.4.1", diff --git a/ComApp/src/App.js b/ComApp/src/App.js index 7a93db9..970ca30 100644 --- a/ComApp/src/App.js +++ b/ComApp/src/App.js @@ -1,51 +1,167 @@ -/** - * Sample React Native App - * https://github.com/facebook/react-native - * - * @format - * @flow strict-local - */ - +import 'react-native-gesture-handler'; import React from 'react'; -import { View, Image, TextInput, TouchableOpacity } from 'react-native'; -// Redux +import { createStore } from 'redux'; import { Provider } from 'react-redux'; -// Store -import { store } from './App/Store/ConfigureStore'; -// Navigation import { NavigationContainer } from '@react-navigation/native'; import { createNativeStackNavigator } from '@react-navigation/native-stack'; -import CustomNavigation from './Navigation'; -// Icons -import FontAwesome5 from 'react-native-vector-icons/FontAwesome5'; -// Flash Message -import FlashMessage from 'react-native-flash-message'; -// Translations -import i18n from "./I18n"; -// Navigations style -import styles from './Resources/Styles/NavigationStyle'; -// Database -import { Setting } from "./Database"; -// Views -import InstallScreen from './App/Views/InstallScreen'; -import HomeScreen from './App/Views/HomeScreen'; -import LoginScreen from './App/Views/LoginScreen'; -import SyncScreen from './App/Views/SyncScreen'; -import DirectoriesScreen from './App/Views/DirectoriesScreen'; -import PDFScreen from './App/Views/PDFScreen'; -import VideoScreen from './App/Views/VideoScreen'; -import OnlyVideosScreen from './App/Views/OnlyVideosScreen'; -import ContactScreen from './App/Views/ContactScreen'; -import ContactDetailsScreen from './App/Views/ContactDetailsScreen'; -import SearchScreen from './App/Views/SearchScreen'; -import MySpaceScreen from './App/Views/MySpaceScreen'; -// Lang Flags -import flagFR from './Resources/assets/img/flags/fr.png'; -import flagEN from './Resources/assets/img/flags/en.png'; -import flagDE from './Resources/assets/img/flags/de.png'; -import flagES from './Resources/assets/img/flags/es.png'; -import SyncController from './App/Controllers/SyncController'; +import { TouchableOpacity, View, Image, Text, TextInput, StyleSheet, Alert } from 'react-native'; +import Icon from 'react-native-vector-icons/FontAwesome5'; +import FlashMessage, { showMessage } from 'react-native-flash-message'; + +import LoginScreen from './Views/Login'; +import HomeScreen from './Views/Home'; +import DirectoriesScreen from './Views/Directories'; +import VideoScreen from './Views/Video'; +import PDFScreen from './Views/PDF'; +import SyncScreen from './Views/Sync'; +import ContactScreen from './Views/Contact'; +import ContactDetailsScreen from './Views/ContactDetails'; +import OnlyVideosScreen from './Views/OnlyVideos'; +import SearchScreen from "./Views/Search"; +import MySpaceScreen from './Views/MySpace'; + +import UsersModel from './Models/Users'; +import DirectoriesModel from './Models/Directories'; +import DocumentsModel from './Models/Documents'; +import ContactsModel from './Models/Contacts'; +import SendBrochureModel from './Models/SendBrochure'; +import CommonModel from './Models/Common'; + +import Sync from './Controllers/Sync'; +import Helpers from "./Controllers/Helpers"; +import i18n from "./I18n"; + +const initialState = { + locale: 'fr', + users: {}, + directories: {}, + documents: {}, + contacts: {}, + syncVersion: 0, + logged: undefined, + currentUser: {}, + currentOpenedDoc: {}, + ShowIsActive: 0, +}; + +function reducer(state = initialState, action) { + //console.log('reducer', state, action); + //console.log('DISPATCH'); + + switch (action.type) { + case 'INIT': + let docs = DocumentsModel.getAll(DirectoriesModel.getAll( + state.currentUser.role, + state.currentUser.authorizedFolders, + false, + state.locale + )); + return { + ...state, + users: UsersModel.getAll(), + directories: DirectoriesModel.getAll( + state.currentUser.role, + state.currentUser.authorizedFolders, + true, + state.locale + ), + documents: docs.filter(item => item.lang === state.locale), + contacts: ContactsModel.getByUser(state.currentUser.id), + ShowIsActive: CommonModel.get('ShowIsActive')['value'], + }; + case 'INIT_USERS': + return { + ...state, + users: UsersModel.getAll(), + }; + case 'SET_CURRENT_USER': + return { + ...state, + currentUser: JSON.parse(action.data), + }; + case 'SET_LOGGED': + if (action.data === false) { + CommonModel.delete('isLogged'); + } + return { + ...state, + logged: action.data, + }; + case 'UPDATE_LOCALE': + let nxt; + if (action.data === undefined) { + const availableLocales = ['fr', 'en', 'de']; + if (availableLocales.indexOf(i18n.locale) === availableLocales.length - 1) { + nxt = availableLocales[0]; + } else { + nxt = availableLocales[availableLocales.indexOf(i18n.locale) + 1]; + } + } else { + nxt = action.data; + } + i18n.locale = nxt; + CommonModel.update({ key: 'locale', value: nxt }); + let docsL = DocumentsModel.getAll(DirectoriesModel.getAll( + state.currentUser.role, + state.currentUser.authorizedFolders, + false, + nxt + )); + return { + ...state, + locale: nxt, + directories: DirectoriesModel.getAll( + state.currentUser.role, + state.currentUser.authorizedFolders, + true, + nxt + ), + documents: docsL.filter(item => item.lang === nxt), + }; + case 'SET_CURRENT_OPENED_DOC': + return { + ...state, + currentOpenedDoc: action.data + }; + case 'UPDATE_VERSION': + return { + ...state, + syncVersion: action.data, + }; + case 'UPDATE_SHOW_IS_ACTIVE': + Sync.updateShowIsActive(action.data); + return { + ...state, + ShowIsActive: action.data, + }; + case 'UPDATE_CONTACT': + ContactsModel.update(action.data); + return { + ...state, + contacts: ContactsModel.getByUser(state.currentUser.id), + }; + case 'ADD_CONTACT': + ContactsModel.add(action.data); + return { + ...state, + contacts: ContactsModel.getByUser(state.currentUser.id), + }; + case 'DELETE_CONTACT': + ContactsModel.delete(action.data); + return { + ...state, + contacts: ContactsModel.getByUser(state.currentUser.id), + }; + case 'SEND_BROCHURE': + SendBrochureModel.add(action.data); + return state; + default: + return state; + } +} + +const store = createStore(reducer); const Stack = createNativeStackNavigator(); store.dispatch({ type: 'INIT_USERS' }); @@ -58,56 +174,134 @@ class App extends React.Component { ShowIsActive: '0' }; - componentDidMount() { - store.subscribe(this.handleChange.bind(this)); - if (SyncController.isInstalled() && SyncController.isLogged()) { + componentDidMount(): void { + const unsubscribe = store.subscribe(this.handleChange.bind(this)); + if (Sync.isInstalled() && Sync.isLogged()) { store.dispatch({ type: 'SET_CURRENT_USER', - data: Setting.get('isLogged').value, + data: CommonModel.get('isLogged').value, }); store.dispatch({ type: 'SET_LOGGED', data: true }); } - const savedLocale = Setting.get('locale'); + const savedLocale = CommonModel.get('locale'); if (savedLocale !== undefined && savedLocale !== false) { store.dispatch({ type: 'UPDATE_LOCALE', data: savedLocale.value }); } } + handleChange() { + const logged = store.getState().logged; + if (logged === false) { + CommonModel.delete('isLogged'); + } + if (logged !== undefined) { + this.setState({ logged: logged }); + } + this.setState({ ShowIsActive: store.getState().ShowIsActive }); + } + + LogoTitle(title) { + return ( + + + {title} + + ); + } + + SendEmail() { + return ( + + this.setState({ sendEmail: text })} /> + { + if (this.state.sendEmail.length > 0) { + if (Helpers.isEmailValid(this.state.sendEmail)) { + const email = { + email: this.state.sendEmail, + subject: i18n.t('app.file_sharing'), + message: '', + documents: JSON.stringify([store.getState().currentOpenedDoc]), + user_id: store.getState().currentUser.id + }; + store.dispatch({ type: 'SEND_BROCHURE', data: email }); + showMessage({ + message: i18n.t('global.message_sent') + '.', + description: i18n.t('global.message_will_be_sent_after_sync') + '.', + type: 'success', + duration: 1500, + }); + this.setState({ sendEmail: '' }); + } else { + Alert.alert(i18n.t('global.warning') + ' !', i18n.t('error.invalid_email_address') + '.', [ + { text: 'OK' }, + ], + { cancelable: false }, + ); + } + } + }}> + + + + ); + } + render() { - const savedLocale = Setting.get('locale'); + const savedLocale = CommonModel.get('locale'); let locale; if (savedLocale !== undefined) { locale = savedLocale.value; } else { - locale = store.getState().app.locale; + locale = store.getState().locale; } - let flag; - switch (locale) { - case "fr": - flag = { uri: Image.resolveAssetSource(flagFR).uri } + switch (locale) { + case "fr": + flag = require('./assets/img/flags/fr.png'); break; - case "en": - flag = { uri: Image.resolveAssetSource(flagEN).uri } + case "en": + flag = require('./assets/img/flags/en.png'); break; - case "de": - flag = { uri: Image.resolveAssetSource(flagDE).uri } + case "de": + flag = require('./assets/img/flags/de.png'); break; - case "es": - flag = { uri: Image.resolveAssetSource(flagES).uri } + case "es": + flag = require('./assets/img/flags/es.png'); break; - } - - return ( - - - - {this.state.logged === false ? this.renderInstallOrLogin(flag) : ( + } + return ( + + + + {this.state.logged === false ? ( + ({ + headerTitle: () => this.LogoTitle(i18n.t('global.login')), + headerRight: () => ( + + { + store.dispatch({ type: 'UPDATE_LOCALE' }); + this.setState({ state: this.state }); // to force re-rendering + }} + style={{ marginRight: 15 }}> + + + + ), + })} + /> + ) : ( ({ - headerTitle: () => CustomNavigation.LogoTitle(i18n.t('global.home')), + headerTitle: () => this.LogoTitle(i18n.t('global.home')), headerRight: () => ( @@ -121,7 +315,7 @@ class App extends React.Component { this.setState({ searchTxt: '' }); } }}> - + navigation.navigation.navigate('MySpaceScreen')} style={{ marginRight: 15 }}> - + )} - + { store.dispatch({ type: 'SET_LOGGED', data: false }); }} style={{ marginRight: 15 }}> - + - ) + ), })} /> - )} - CustomNavigation.LogoTitle(i18n.t('global.synchronization')) }} - /> - CustomNavigation.LogoTitle(i18n.t('app.folders')) }} - /> - ({ - headerTitle: () => CustomNavigation.LogoTitle('PDF') - })} - /> - ({ - headerTitle: () => CustomNavigation.LogoTitle(i18n.t('app.video')) - })} - /> - ({ - headerTitle: () => CustomNavigation.LogoTitle(i18n.t('global.videos')), - headerRight: () => ( - - this.setState({ searchTxt: text })} /> - { - if (this.state.searchTxt.length > 0) { - navigation.navigation.navigate('SearchScreen', { - needle: this.state.searchTxt, - onlyVideos: true, - }); - this.setState({ searchTxt: '' }); - } - }}> - - - - ), - })} - /> - CustomNavigation.LogoTitle(i18n.t('global.contacts')) }} - initialParams={{ contactAdded: false }} - /> - CustomNavigation.LogoTitle(i18n.t('app.contact_details')) }} - /> - CustomNavigation.LogoTitle(i18n.t('app.results_search')) }} - /> - ({ - headerTitle: () => CustomNavigation.LogoTitle(i18n.t('app.my_space')), - headerRight: () => ( - - { - const folder = store.getState().directories.filter(tmp => tmp.id === 131); - navigation.navigation.navigate('DirectoriesScreen', { folder: folder }) - }} - style={{ marginRight: 15 }}> - - - - ), - })} - /> - - - - - ); - } - - handleChange() { - const logged = store.getState().app.logged; - if (logged === false) { - Setting.delete('isLogged'); - } - if (logged !== undefined) { - this.setState({ logged: logged }); - } - this.setState({ ShowIsActive: store.getState().app.ShowIsActive }); - } - - renderInstallOrLogin(flag) { - if (SyncController.isInstalled()) { - return ( - ({ - headerTitle: () => CustomNavigation.LogoTitle(i18n.t('global.login')), - headerRight: () => ( - - { - store.dispatch({ type: 'UPDATE_LOCALE' }); - this.setState({ state: this.state }); // to force re-rendering - }} - style={{ marginRight: 15 }}> - - - - ), - })} - /> - ); - } else { - return ( - ({ - headerTitle: () => CustomNavigation.LogoTitle(i18n.t('install.installation')), - headerRight: () => ( - - { - store.dispatch({ type: 'UPDATE_LOCALE' }); - this.setState({ state: this.state }); // to force re-rendering - }} - style={{ marginRight: 15 }}> - - - - ), - })} - /> - ); - } + )} + this.LogoTitle(i18n.t('global.synchronization')) }} + /> + this.LogoTitle(i18n.t('app.folders')) }} + /> + ({ + headerTitle: () => this.LogoTitle(i18n.t('global.videos')), + headerRight: () => ( + + this.setState({ searchTxt: text })} /> + { + if (this.state.searchTxt.length > 0) { + navigation.navigation.navigate('SearchScreen', { + needle: this.state.searchTxt, + onlyVideos: true, + }); + this.setState({ searchTxt: '' }); + } + }}> + + + + ), + })} + /> + ({ + headerTitle: () => this.LogoTitle(i18n.t('app.video')), + headerRight: () => this.SendEmail() + })} + /> + ({ + headerTitle: () => this.LogoTitle('PDF'), + headerRight: () => this.SendEmail() + })} + /> + this.LogoTitle(i18n.t('global.contacts')) }} + initialParams={{ contactAdded: false }} + /> + this.LogoTitle(i18n.t('app.contact_details')) }} + /> + this.LogoTitle(i18n.t('app.results_search')) }} + /> + ({ + headerTitle: () => this.LogoTitle(i18n.t('app.my_space')), + headerRight: () => ( + + { + const folder = store.getState().directories.filter(tmp => tmp.id === 131); + navigation.navigation.navigate('DirectoriesScreen', { folder: folder }) + }} + style={{ marginRight: 15 }}> + + + + ), + })} + /> + + + + + ); } } -export default App; \ No newline at end of file +const styles = StyleSheet.create({ + searchInput: { + borderWidth: 1, + borderColor: '#ce3029', + width: 200, + paddingLeft: 20, + }, + btnSearch: { + width: 50, + height: 50, + alignItems: 'center', + justifyContent: 'center', + backgroundColor: '#ce3029' + } +}); + +export default App; diff --git a/ComApp/src/Controllers/Helpers.js b/ComApp/src/Controllers/Helpers.js new file mode 100644 index 0000000..7e55261 --- /dev/null +++ b/ComApp/src/Controllers/Helpers.js @@ -0,0 +1,47 @@ +class Helpers { + compareValues(key, order = 'asc') { + return function innerSort(a, b) { + if (!a.hasOwnProperty(key) || !b.hasOwnProperty(key)) { + // property doesn't exist on either object + return 0; + } + + const varA = (typeof a[key] === 'string') + ? a[key].toUpperCase() : a[key]; + const varB = (typeof b[key] === 'string') + ? b[key].toUpperCase() : b[key]; + + let comparison = 0; + if (varA > varB) { + comparison = 1; + } else if (varA < varB) { + comparison = -1; + } + return ( + (order === 'desc') ? (comparison * -1) : comparison + ); + }; + } + + isEmailValid(email) { + const expression = /(?!.*\.{2})^([a-z\d!#$%&'*+\-\/=?^_`{|}~\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+(\.[a-z\d!#$%&'*+\-\/=?^_`{|}~\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+)*|"((([\t]*\r\n)?[\t]+)?([\x01-\x08\x0b\x0c\x0e-\x1f\x7f\x21\x23-\x5b\x5d-\x7e\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]|\\[\x01-\x09\x0b\x0c\x0d-\x7f\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))*(([\t]*\r\n)?[\t]+)?")@(([a-z\d\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]|[a-z\d\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF][a-z\d\-._~\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]*[a-z\d\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])\.)+([a-z\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]|[a-z\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF][a-z\d\-._~\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]*[a-z\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])\.?$/i; + return expression.test(String(email).toLowerCase()); + } + + generateUUID() { // Public Domain/MIT + let d = new Date().getTime();//Timestamp + let d2 = new Date().getUTCMilliseconds() + Math.random();//Time in microseconds since page-load or 0 if unsupported + return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { + let r = Math.random() * 16;//random number between 0 and 16 + if(d > 0){//Use timestamp until depleted + r = (d + r)%16 | 0; + d = Math.floor(d/16); + } else {//Use microseconds since page-load if supported + r = (d2 + r)%16 | 0; + d2 = Math.floor(d2/16); + } + return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16); + }); + } +} +export default new Helpers(); diff --git a/ComApp/src/Controllers/Sync.js b/ComApp/src/Controllers/Sync.js new file mode 100644 index 0000000..1893368 --- /dev/null +++ b/ComApp/src/Controllers/Sync.js @@ -0,0 +1,270 @@ +import NetInfo from '@react-native-community/netinfo'; +import Constants from 'expo-constants'; + +const axios = require('axios'); +const api_url = 'https://app.maitrecoq.fr'; +const api_token = 'Np5smPey2q94HxY8xV27c2TN7C6VpC'; +const endpoint = api_url + '/api'; +const app_uuid = '1f6eda31-1f6d-49fe-aa81-4f0aece0f7ca'; + +import UsersModel from '../Models/Users'; +import DirectoriesModel from '../Models/Directories'; +import DocumentsModel from '../Models/Documents'; +import ContactsModel from '../Models/Contacts'; +import CommonModel from '../Models/Common'; +import SendBrochureModel from '../Models/SendBrochure'; + +class Sync { + getEndpointDocs() { + return api_url + '/storage/docs/'; + } + + getApiToken() { + return api_token; + } + + getAppUUID() { + return app_uuid; + } + + getVersion(callback, errorCallback) { + axios({ + method: 'get', + url: endpoint + '/version', + responseType: 'json', + params: { + token: this.getApiToken(), + app_uuid: app_uuid, + app_version: Constants.nativeAppVersion + }, + }) + .then(response => { + return callback(response.data.version, response.data.ShowIsActive, response.data.newApp); + }) + .catch(function(error) { + console.log(error); + return errorCallback(error); + }); + } + + needSync(callback) { + NetInfo.fetch().then(state => { + if (state.isConnected) { + axios({ + method: 'get', + url: endpoint + '/version', + responseType: 'json', + params: { + token: this.getApiToken(), + }, + }) + .then(response => { + const localVersion = CommonModel.get('version'); + if ( + localVersion === undefined || + localVersion === false || + localVersion.value !== response.data.version + ) { + return callback(); + } + return false; + }) + .catch(function(error) { + console.log(error); + }); + } + return false; + }); + return false; + } + + setInstalled() { + CommonModel.add({key: 'installed', value: '1'}); + CommonModel.add({key: 'locale', value: 'fr'}); + CommonModel.add({key: 'ShowIsActive', value: '0'}); + } + + getUsers(callback) { + axios({ + method: 'get', + url: endpoint + '/users', + responseType: 'json', + params: { + token: this.getApiToken(), + }, + }) + .then(response => { + UsersModel.clear(); + response.data.forEach(user => { + UsersModel.add(user); + }); + console.log('Users Update Done'); + return callback(); + }) + .catch(function(error) { + console.log(error); + }); + } + + getDirectories(callback) { + axios({ + method: 'get', + url: endpoint + '/directories', + responseType: 'json', + params: { + token: this.getApiToken(), + }, + }) + .then(function(response) { + DirectoriesModel.clear(); + response.data.forEach(dir => { + DirectoriesModel.add(dir); + }); + console.log('Directories Update Done'); + return callback(); + }) + .catch(function(error) { + console.log(error); + }); + } + + getDocuments(callback) { + axios({ + method: 'get', + url: endpoint + '/documents', + responseType: 'json', + params: { + token: this.getApiToken(), + }, + }) + .then(response => { + const toDelete = DocumentsModel.clear(response.data); + response.data.forEach(document => { + DocumentsModel.update(document); + }); + console.log('Documents Update Done'); + return callback(DocumentsModel.getAll(), toDelete); + }) + .catch(function(error) { + console.log(error); + }); + } + + getContacts(callback, userId) { + let contacts = ContactsModel.getAll(); + axios({ + method: 'post', + url: endpoint + '/contacts' + '?token=' + this.getApiToken(), + responseType: 'json', + data: { + contacts: contacts, + userId: userId, + }, + }) + .then(response => { + ContactsModel.clear(); + response.data.forEach(contact => { + ContactsModel.add(contact); + }); + console.log('Contacts Update Done'); + callback(); + }) + .catch(function(error) { + console.log(error); + }); + } + + async sendBrochure(brochures, i) { + if (brochures.length > 0) { + const item = brochures[i]; + let response = await axios({ + method: 'post', + url: endpoint + '/sendBrochure', + responseType: 'json', + params: { + token: this.getApiToken(), + emails: {item}, + }, + }); + if (i < brochures.length - 1) { + return this.sendBrochure(brochures, i + 1); + } + return true; + } + return true; + } + + sendBrochures(callback) { + this.sendBrochure(SendBrochureModel.getAll(), 0).then(res => { + SendBrochureModel.clear(); + console.log('SendBrochures Update Done'); + callback(); + }); + } + + updateVersion(version) { + const localVersion = CommonModel.get('version'); + if (localVersion === false || localVersion === undefined) { + CommonModel.add({key: 'version', value: version}); + } else { + CommonModel.update({id: localVersion.id, key: 'version', value: version}); + } + } + + updateShowIsActive(value) { + const local = CommonModel.get('ShowIsActive'); + if (local === false || local === undefined) { + CommonModel.add({key: 'ShowIsActive', value: value}); + } else { + CommonModel.update({id: local.id, key: 'ShowIsActive', value: value}); + } + } + + isLogged() { + const logged = CommonModel.get('isLogged'); + const loggedTime = CommonModel.get('loggedTime'); + if ( + logged !== false && + loggedTime !== false && + logged !== undefined && + loggedTime !== undefined + ) { + let seconds = + Math.abs(new Date(Number(loggedTime.value)) - Date.now()) / 1000; + if (seconds < 14400) { + // 4h + return true; + } + return false; + } + return false; + } + + isInstalled() { + const test = CommonModel.get('installed'); + if (test === false) { + return false; + } + return true; + } + + updateLastModificationDoc(id, date) { + DocumentsModel.update({id: id, updated_at: date.toString()}, true); + } + + isSynced() { + const SendBrochure = SendBrochureModel.getAll(); + const Contacts = ContactsModel.getAll(); + + if (SendBrochure !== undefined && Contacts !== undefined) { + const noSynced = Contacts.filtered('synced = false'); + if (SendBrochure.length > 0 || noSynced.length > 0) { + return false; + } + } + return true; + } + +} + +export default new Sync(); diff --git a/ComApp/src/Database.js b/ComApp/src/Database.js index 0c6f026..e2fb0f5 100644 --- a/ComApp/src/Database.js +++ b/ComApp/src/Database.js @@ -1,127 +1,103 @@ -import Realm from "realm"; -// Models -import SettingModel from "./App/Models/Setting"; -import UserModel from './App/Models/User'; -import ContactModel from './App/Models/Contact'; -import SendBrochureModel from "./App/Models/SendBrochure"; -import DirectoryModel from "./App/Models/Directory"; -import DocumentModel from './App/Models/Document'; - -const SettingSchema = { - name: 'Settings', - primaryKey: 'id', - properties: { - id: 'int', - key: 'string', - value: 'string', - }, -}; - -const UserSchema = { - name: 'Users', - primaryKey: 'id', - properties: { - id: 'int', - email: 'string', - surname: 'string', - firstname: 'string', - password: 'string', - role: 'string', - authorizedFolders: 'string', - }, -}; - -const ContactSchema = { - name: 'Contacts', - primaryKey: 'id', - properties: { - id: 'int', - email: 'string', - type: 'string', - other: { type: 'string', optional: true }, - select1: { type: 'string', optional: true }, - select2: { type: 'string', optional: true }, - name: 'string', - firstname: 'string', - company: { type: 'string', optional: true }, - address: { type: 'string', optional: true }, - phone: 'string', - products: { type: 'string', optional: true }, - // TODO : card_url ? - notes: { type: 'string', optional: true }, - visits: { type: 'string', optional: true }, - user_id: 'int', - uniq_id: 'string', - to_delete: { type: 'bool', default: false }, - synced: { type: 'bool', default: true }, - }, -}; - -const SendBrochureSchema = { - name: 'SendBrochures', - primaryKey: 'id', - properties: { - id: 'int', - email: 'string', - subject: 'string', - message: 'string', - documents: 'string', - user_id: 'int' - }, -}; - -const DirectorySchema = { - name: 'Directories', - primaryKey: 'id', - properties: { - id: 'int', - name: 'string', - parent: { type: 'int', optional: true }, - order: 'int', - needAuthorization: 'int', - }, -}; - -const DocumentSchema = { - name: 'Documents', - primaryKey: 'id', - properties: { - id: 'int', - uuid: 'string', - title: 'string', - type: 'string', - path: 'string', - directory: 'int', - lang: 'string', - updated_at: 'string', - }, -}; - -const Database = { - schema: [ - SettingSchema, - UserSchema, - ContactSchema, - SendBrochureSchema, - DirectorySchema, - DocumentSchema - ], - //encryptionKey: new Int8Array(64) - }; - -let realm = new Realm(Database); -let Setting = new SettingModel(realm); -let User = new UserModel(realm); -let Contact = new ContactModel(realm); -let SendBrochure = new SendBrochureModel(realm); -let Directory = new DirectoryModel(realm); -let Document = new DocumentModel(realm); - -export { - Setting, - User, - Contact, - SendBrochure, - Directory, - Document -} \ No newline at end of file +const CommonSchema = { + name: 'Common', + primaryKey: 'id', + properties: { + id: 'int', + key: 'string', + value: 'string', + }, +}; + +const UserSchema = { + name: 'Users', + primaryKey: 'id', + properties: { + id: 'int', + email: 'string', + surname: 'string', + firstname: 'string', + password: 'string', + role: 'string', + authorizedFolders: 'string', + }, +}; + +const DirectoriesSchema = { + name: 'Directories', + primaryKey: 'id', + properties: { + id: 'int', + name: 'string', + parent: { type: 'int', optional: true }, + order: 'int', + needAuthorization: 'int', + }, +}; + +const DocumentsSchema = { + name: 'Documents', + primaryKey: 'id', + properties: { + id: 'int', + uuid: 'string', + title: 'string', + type: 'string', + path: 'string', + directory: 'int', + lang: 'string', + updated_at: 'string', + }, +}; + +const ContactsSchema = { + name: 'Contacts', + primaryKey: 'id', + properties: { + id: 'int', + email: 'string', + type: 'string', + other: { type: 'string', optional: true }, + select1: { type: 'string', optional: true }, + select2: { type: 'string', optional: true }, + name: 'string', + firstname: 'string', + company: { type: 'string', optional: true }, + address: { type: 'string', optional: true }, + phone: 'string', + products: { type: 'string', optional: true }, + // TODO : card_url ? + notes: { type: 'string', optional: true }, + visits: { type: 'string', optional: true }, + user_id: 'int', + uniq_id: 'string', + to_delete: { type: 'bool', default: false }, + synced: { type: 'bool', default: true }, + }, +}; + +const SendBrochureSchema = { + name: 'SendBrochure', + primaryKey: 'id', + properties: { + id: 'int', + email: 'string', + subject: 'string', + message: 'string', + documents: 'string', + user_id: 'int' + }, +}; + +const Database = { + schema: [ + CommonSchema, + UserSchema, + DirectoriesSchema, + DocumentsSchema, + ContactsSchema, + SendBrochureSchema, + ], + //encryptionKey: new Int8Array(64) +}; + +export default Database; diff --git a/ComApp/src/I18n.js b/ComApp/src/I18n.js index 45fed4e..3b94187 100644 --- a/ComApp/src/I18n.js +++ b/ComApp/src/I18n.js @@ -1,17 +1,18 @@ -import { I18n } from 'i18n-js'; - -import fr from './Resources/Locales/fr'; -import en from './Resources/Locales/en'; -import de from './Resources/Locales/de'; - -const i18n = new I18n({ - fr: fr, - en: en, - de: de -}); - -i18n.defaultLocale = 'fr'; -i18n.locale = 'fr'; -i18n.enableFallback = true; - -export default i18n; +import { I18n } from 'i18n-js'; + +import fr from './Locales/fr'; +import en from './Locales/en'; +import de from './Locales/de'; + +const i18n = new I18n({ + fr: fr, + en: en, + de: de +}); + +i18n.defaultLocale = 'fr'; +i18n.locale = 'fr'; +i18n.fallbacks = true; +i18n.translations = { fr, en, de }; + +export default i18n; diff --git a/ComApp/src/Locales/de.json b/ComApp/src/Locales/de.json new file mode 100644 index 0000000..bc56844 --- /dev/null +++ b/ComApp/src/Locales/de.json @@ -0,0 +1,157 @@ +{ + "global": { + "yes": "Ja", + "no": "Nein", + "home": "Startseite", + "search": "Suche", + "login": "Verbindung", + "email": "Email", + "message_sent": "Email geschickt", + "message_will_be_sent_after_sync": "Die Nachricht wird nach Synchronisation geschickt", + "warning": "Achtung", + "synchronization": "Synchronisation ", + "videos": "Videos", + "contacts": "Kontakt", + "confirmation": "Bestätigung", + "name": "Name", + "firstname": "Vorname", + "address": "Adresse", + "phone": "Telefonnummer", + "company": "Firma", + "add": "Addieren", + "cancel": "Abbrechen", + "subject": "Betreff", + "message": "Nachricht", + "send": "Senden", + "required": "erforderlich", + "save": "Speichern", + "retry": "Noch einmal versuchen", + "next": "Nächstes" + }, + "app": { + "file_sharing": " Dateifreigabe", + "folders": "Ordner", + "video": "Video", + "contact_details": "Kontaktdaten", + "results_search": "Suchergebnisse", + "my_space": "Mein Raum" + }, + "login_s": { + "install": "Installieren", + "username": "Benutzername", + "password": "Passwort" + }, + "home_s": { + "1": "Kataloge", + "2": "Datenblätter", + "3": "Andere" + }, + "search_s": { + "no_result": "Keine Ergebnisse" + }, + "sync_s": { + "download": "Download", + "update_completed": "Update abgeschlossen", + "updating_docs": "Dokumentaktualisierung", + "updating_folders": "Dateien aktualisieren", + "sending_flyers": "Brochure senden", + "updating_contacts": "Kontakt aktualisieren", + "user_install_completed": "Benutzerinstallation abgeschlossen", + "updating_users": "Benutzer-Update", + "user_install": "Benutzerinstallation", + "start_sync": "Synchronisation starten", + "new_app_version": "Eine neue Version der Anwendung ist verfügbar!", + "open_store": "Öffnen Sie den App Store" + }, + "contact_s": { + "contact_transferred": "Kontakt weiterleiten", + "contact_will_be_sent_after_sync": "Der Kontakt wird nach der Synchronisation gesendet", + "confirm_contact_delete": "Möchten Sie diesen Kontakt wirklich löschen?", + "contact_deleted": "Kontakt gelöscht", + "contact_added": "Kontakt hinzugefügt", + "send_flyer": "Senden Sie eine Broschüre", + "select_contact": "Wählen Sie den Kontakt", + "transfer": "weiterleiten", + "contact_list": "Kontaktliste", + "documents_list": "Liste der Dokumente" + }, + "contact_details_s": { + "visits": "Besuche", + "notes": "Notes", + "contact_saved": "Kontakt gespeichert", + "confirm_visit_delete": "Sind Sie sicher, dass Sie diesen Besuch löschen möchten?", + "qrcode_not_vcard_format": "Dieser QRCode ist nicht im vCard-Format", + "customer": "Kunde", + "prospect": "potenzieller Kunde", + "other": "Andere", + "description": "Beschreibung", + "no_visits": "Kein Besuch", + "visit_name": "Besuchsname" + }, + "error": { + "error": "Fehler ", + "tablet_not_connected_internet": "Das Tablet ist nicht mit dem Internet verbunden.", + "tablet_not_synced_last_user": "Das Tablet wurde vom vorherigen Benutzer nicht synchronisiert", + "incorrect_credentials": "Falsche Anmeldeinformationen", + "must_fill_login_fields": "Sie müssen die Verbindungsfelder ausfüllen", + "invalid_email_address": "Ungültige E-Mail-Adresse", + "error_has_occurred": "Ein Fehler ist aufgetreten", + "must_be_connected_internet_sync": "Sie müssen mit dem Internet verbunden sein, um die Synchronisierung zu starten", + "incomplete_form": "Unvollständiges Formular", + "fill_all_fields_and_1_doc_min": "Bitte füllen Sie alle Felder, mindestens 1 Dokument und eine gültige E-Mail-Adresse aus.", + "fill_other_field": "Füllen Sie das Beschreibungsfeld für: Andere", + "specify_type_contact": "Bitte geben Sie die Art des Kontakts an", + "invalid_name": "ungültiger Name", + "invalid_firstname": "ungültiger Vorname", + "camera_permission_denied": "Kameraerlaubnis verweigert", + "pending_camera_permission": "Warten auf Kameraerlaubnis" + }, + "custom": { + "select-1": { + "title": "Type de commerce", + "c1": "Commerce de détail", + "c2": "Home Service / Freezer Center", + "c3": "Restauration service à table", + "c4": "Fast food et snacking", + "c5": "Cafétaria / Self Service", + "c6": "Société de restauration", + "c7": "Restauration gré à gré / cuisine Centrale", + "c8": "Distributeur indépendant", + "c9": "Boulangerie pâtisserie", + "c10": "PAI", + "c11": "Destockeur", + "c12": "Commerce Halal", + "c13": "Restauration d'hébergement", + "c14": "Centrale restauration commerciale", + "c15": "Restauration évenementielle et parc", + "c16": "Restauration de transport", + "c17": "Marché public", + "c18": "Cash", + "c19": "Distributeur national", + "c20": "Rungis", + "c21": "Abbatoir", + "c22": "Autre" + }, + "select-2": { + "title": "Site", + "c1": "Celvia Dinde", + "c2": "Celvia Elaborés", + "c3": "Celvia Sérent", + "c4": "Procanar", + "c5": "Celtys", + "c6": "Farmor", + "c7": "Robichon", + "c8": "Les Volailles de Kerana", + "c9": "Bosher Volaille", + "c10": "LDC Bretagne", + "c11": "Doux Farmor", + "c12": "Favreau" + }, + "interest": { + "p1": "Poulet", + "p2": "Dinde", + "p3": "Canard", + "p4": "Autre" + } + } +} diff --git a/ComApp/src/Locales/en.json b/ComApp/src/Locales/en.json new file mode 100644 index 0000000..7d61405 --- /dev/null +++ b/ComApp/src/Locales/en.json @@ -0,0 +1,157 @@ +{ + "global": { + "yes": "Yes", + "no": "No", + "home": "Home", + "search": "Search", + "login": "Login", + "email": "Email", + "message_sent": "Message Sent", + "message_will_be_sent_after_sync": "Message will be send after synchronization", + "warning": "Warning", + "synchronization": "Synchronization", + "videos": "Videos", + "contacts": "Contacts", + "confirmation": "Confirmation", + "name": "Lastname", + "firstname": "Firstname", + "address": "Adress", + "phone": "Phone number", + "company": "Company", + "add": "Add", + "cancel": "Cancel", + "subject": "Object", + "message": "Message", + "send": "Send", + "required": "required", + "save": "Save", + "retry": "Retry", + "next": "Next" + }, + "app": { + "file_sharing": "File Sharing", + "folders": "Folders", + "video": "Video", + "contact_details": "Contact information", + "results_search": "Search results", + "my_space": "Personal Dashboard" + }, + "login_s": { + "install": "Install", + "username": "Username", + "password": "Password" + }, + "home_s": { + "1": "Catalogs", + "2": "Data sheets", + "3": "Others" + }, + "search_s": { + "no_result": "No Result" + }, + "sync_s": { + "download": "Download", + "update_completed": "Update Done", + "updating_docs": "Updating documents", + "updating_folders": "Updating folders", + "sending_flyers": "Sending documents", + "updating_contacts": "Updating contacts", + "user_install_completed": "User install completed", + "updating_users": "Updating Users", + "user_install": "Installing Users", + "start_sync": "START SYNCHRONIZATION", + "new_app_version": "A new version of the application is available!", + "open_store": "Open the store" + }, + "contact_s": { + "contact_transferred": "Contact Transferred", + "contact_will_be_sent_after_sync": "The contact will be sent after synchronization", + "confirm_contact_delete": "Are you sure to delete this contact ?", + "contact_deleted": "Contact deleted", + "contact_added": "Contact added", + "send_flyer": "Send a document", + "select_contact": "Select the contact", + "transfer": "Transfer", + "contact_list": "Contact List", + "documents_list": "Documents List" + }, + "contact_details_s": { + "visits": "Visits", + "notes": "Notes", + "contact_saved": "Contact saved", + "confirm_visit_delete": "are you sure to delete this visit ?", + "qrcode_not_vcard_format": "This QRCode is not a VCard", + "customer": "Customer", + "prospect": "Prospect", + "other": "Other", + "description": "Description", + "no_visits": "No visits available", + "visit_name": "visits name" + }, + "error": { + "error": "Error", + "tablet_not_connected_internet": "The tablet is not connected to Internet", + "tablet_not_synced_last_user": "The previous user didn't synchronised the tablet", + "incorrect_credentials": "Incorrect Credentials", + "must_fill_login_fields": "You must fill the login fields.", + "invalid_email_address": "Invalid E-mail adress", + "error_has_occurred": "An error occured", + "must_be_connected_internet_sync": "You must be connected to internet to be able to synchronize", + "incomplete_form": "Incomplete Form", + "fill_all_fields_and_1_doc_min": "Please fill up all the fields, at least one document and a valid e-mail adress", + "fill_other_field": "You must fill the description field for : Other", + "specify_type_contact": "You must specify the contact type", + "invalid_name": "Invalid lastname", + "invalid_firstname": "Invalid firstname", + "camera_permission_denied": "Camera Permission Denied", + "pending_camera_permission": "Waiting for the camera permission" + }, + "custom": { + "select-1": { + "title": "Type de commerce", + "c1": "Commerce de détail", + "c2": "Home Service / Freezer Center", + "c3": "Restauration service à table", + "c4": "Fast food et snacking", + "c5": "Cafétaria / Self Service", + "c6": "Société de restauration", + "c7": "Restauration gré à gré / cuisine Centrale", + "c8": "Distributeur indépendant", + "c9": "Boulangerie pâtisserie", + "c10": "PAI", + "c11": "Destockeur", + "c12": "Commerce Halal", + "c13": "Restauration d'hébergement", + "c14": "Centrale restauration commerciale", + "c15": "Restauration évenementielle et parc", + "c16": "Restauration de transport", + "c17": "Marché public", + "c18": "Cash", + "c19": "Distributeur national", + "c20": "Rungis", + "c21": "Abbatoir", + "c22": "Autre" + }, + "select-2": { + "title": "Site", + "c1": "Celvia Dinde", + "c2": "Celvia Elaborés", + "c3": "Celvia Sérent", + "c4": "Procanar", + "c5": "Celtys", + "c6": "Farmor", + "c7": "Robichon", + "c8": "Les Volailles de Kerana", + "c9": "Bosher Volaille", + "c10": "LDC Bretagne", + "c11": "Doux Farmor", + "c12": "Favreau" + }, + "interest": { + "p1": "Poulet", + "p2": "Dinde", + "p3": "Canard", + "p4": "Autre" + } + } +} diff --git a/ComApp/src/Locales/fr.json b/ComApp/src/Locales/fr.json new file mode 100644 index 0000000..5596382 --- /dev/null +++ b/ComApp/src/Locales/fr.json @@ -0,0 +1,157 @@ +{ + "global": { + "yes": "Oui", + "no": "Non", + "home": "Accueil", + "search": "Recherche", + "login": "Connexion", + "email": "Email", + "message_sent": "Message envoyé", + "message_will_be_sent_after_sync": "Le message sera envoyé après synchronisation", + "warning": "Attention", + "synchronization": "Synchronisation", + "videos": "Vidéos", + "contacts": "Contacts", + "confirmation": "Confirmation", + "name": "Nom", + "firstname": "Prénom", + "address": "Adresse", + "phone": "Téléphone", + "company": "Société", + "add": "Ajouter", + "cancel": "Annuler", + "subject": "Objet", + "message": "Message", + "send": "Envoyer", + "required": "requis", + "save": "Enregistrer", + "retry": "Réessayer", + "next": "Suivant" + }, + "app": { + "file_sharing": "Partage de fichier", + "folders": "Dossiers", + "video": "Vidéo", + "contact_details": "Détails du contact", + "results_search": "Résultat(s) de votre recherche", + "my_space": "Mon espace" + }, + "login_s": { + "install": "Installer", + "username": "Identifiant", + "password": "Mot de passe" + }, + "home_s": { + "1": "Catalogues", + "2": "Fiches techniques", + "3": "Autres" + }, + "search_s": { + "no_result": "Aucun résultat" + }, + "sync_s": { + "download": "Téléchargement", + "update_completed": "Mise à jour effectuée", + "updating_docs": "Mise à jour des documents", + "updating_folders": "Mise à jour des dossiers", + "sending_flyers": "Envoi des brochures", + "updating_contacts": "Mise à jour des contacts", + "user_install_completed": "Installation des utilisateurs effectuée", + "updating_users": "Mise à jour des utilisateurs", + "user_install": "Installation des utilisateurs", + "start_sync": "LANCER LA SYNCHRONISATION", + "new_app_version": "Une nouvelle version de l'application est disponible !", + "open_store": "Ouvrir le store" + }, + "contact_s": { + "contact_transferred": "Contact transféré", + "contact_will_be_sent_after_sync": "Le contact sera envoyé après synchronisation", + "confirm_contact_delete": "Êtes-vous sûr de vouloir supprimer ce contact ?", + "contact_deleted": "Contact supprimé", + "contact_added": "Contact ajouté", + "send_flyer": "Envoyer une brochure", + "select_contact": "Sélectionnez le contact", + "transfer": "Transférer", + "contact_list": "Liste des contacts", + "documents_list": "Liste des documents" + }, + "contact_details_s": { + "visits": "Visites", + "notes": "Notes", + "contact_saved": "Contact enregistré", + "confirm_visit_delete": "Êtes-vous sûr de vouloir supprimer cette visite ?", + "qrcode_not_vcard_format": "Ce QRCode n'est pas au format vCard", + "customer": "Client", + "prospect": "Prospect", + "other": "Autre", + "description": "Description", + "no_visits": "Aucune visites", + "visit_name": "Nom de la visite" + }, + "error": { + "error": "Erreur", + "tablet_not_connected_internet": "La tablette n'est pas connectée à internet.", + "tablet_not_synced_last_user": "La tablette n'as pas été synchronisée par l'utilisateur précédent.", + "incorrect_credentials": "Identifiants incorrect.", + "must_fill_login_fields": "Vous devez-remplir les champs de connexion.", + "invalid_email_address": "Adresse email invalide", + "error_has_occurred": "Une erreur est survenue", + "must_be_connected_internet_sync": "Vous devez être connecté à internet pour lancer la synchronisation", + "incomplete_form": "Formulaire incomplet", + "fill_all_fields_and_1_doc_min": "Veuillez remplir tous les champs, 1 document minimum et une adresse email valide", + "fill_other_field": "Vous devez remplir le champs description pour : Autre", + "specify_type_contact": "Vous devez préciser le type de contact", + "invalid_name": "Nom invalide", + "invalid_firstname": "Prénom invalide", + "camera_permission_denied": "Permission caméra refusée", + "pending_camera_permission": "En attente de permission caméra" + }, + "custom": { + "select-1": { + "title": "Type de commerce", + "c1": "Commerce de détail", + "c2": "Home Service / Freezer Center", + "c3": "Restauration service à table", + "c4": "Fast food et snacking", + "c5": "Cafétaria / Self Service", + "c6": "Société de restauration", + "c7": "Restauration gré à gré / cuisine Centrale", + "c8": "Distributeur indépendant", + "c9": "Boulangerie pâtisserie", + "c10": "PAI", + "c11": "Destockeur", + "c12": "Commerce Halal", + "c13": "Restauration d'hébergement", + "c14": "Centrale restauration commerciale", + "c15": "Restauration évenementielle et parc", + "c16": "Restauration de transport", + "c17": "Marché public", + "c18": "Cash", + "c19": "Distributeur national", + "c20": "Rungis", + "c21": "Abbatoir", + "c22": "Autre" + }, + "select-2": { + "title": "Site", + "c1": "Celvia Dinde", + "c2": "Celvia Elaborés", + "c3": "Celvia Sérent", + "c4": "Procanar", + "c5": "Celtys", + "c6": "Farmor", + "c7": "Robichon", + "c8": "Les Volailles de Kerana", + "c9": "Bosher Volaille", + "c10": "LDC Bretagne", + "c11": "Doux Farmor", + "c12": "Favreau" + }, + "interest": { + "p1": "Poulet", + "p2": "Dinde", + "p3": "Canard", + "p4": "Autre" + } + } +} diff --git a/ComApp/src/Models/Common.js b/ComApp/src/Models/Common.js new file mode 100644 index 0000000..3d8ab03 --- /dev/null +++ b/ComApp/src/Models/Common.js @@ -0,0 +1,64 @@ +import Database from '../Database'; + +const Realm = require('realm'); +let realm = new Realm(Database); + +class CommonModel { + constructor() { + this.schema = 'Common'; + } + + add(data) { + realm.write(() => { + if (data.id === undefined) { + let id: any = realm.objects(this.schema).max('id'); + data.id = id === undefined ? 1 : id + 1; + } + try { + realm.create(this.schema, data, 'never'); + } catch (e) { + console.log('db common add', e); + } + }); + } + + update(data) { + let tmp = this.get(data.key); + if (tmp === undefined) { + this.add(data); + } else { + if (data.id === undefined) { + data.id = tmp.id; + } + realm.write(() => { + realm.create(this.schema, data, 'modified'); + }); + } + } + + get(key) { + let tmp = realm.objects(this.schema); + if (tmp.length === 0) { + return false; + } else { + tmp = tmp.filtered('key = "' + key + '"')[0]; + } + return tmp; + } + + getAll() { + let tmp = realm.objects(this.schema); + return tmp; + } + + delete(key) { + const tmp = this.get(key); + if (typeof tmp === 'object') { + realm.write(() => { + realm.delete(tmp); + }); + } + } +} + +export default new CommonModel(); diff --git a/ComApp/src/Models/Contacts.js b/ComApp/src/Models/Contacts.js new file mode 100644 index 0000000..8cb1243 --- /dev/null +++ b/ComApp/src/Models/Contacts.js @@ -0,0 +1,60 @@ +import Database from '../Database'; + +const Realm = require('realm'); +let realm = new Realm(Database); + +class ContactsModel { + constructor() { + this.schema = 'Contacts'; + } + + add(contact) { + contact.uniq_id = contact.id; + contact.id = undefined; + realm.write(() => { + if (contact.id === undefined) { + let id: any = realm.objects(this.schema).max('id'); + contact.id = id === undefined ? 1 : id + 1; + } + realm.create(this.schema, contact); + }); + } + + update(contact) { + realm.write(() => { + realm.create(this.schema, contact, true); + }); + } + + get(id) { + let tmp = realm.objects(this.schema).filtered('id = ' + id)[0]; + return tmp; + } + + getAll() { + let tmp = realm.objects(this.schema); + return tmp; + } + + getByUser(userId) { + let tmp = realm.objects(this.schema); + tmp = tmp.filtered('to_delete = false AND user_id = ' + userId); + return tmp; + } + + delete(id) { + const tmp = this.get(id); + realm.write(() => { + tmp.to_delete = true; + // HARD DELETE - > realm.delete(tmp); + }); + } + + clear() { + realm.write(() => { + realm.delete(this.getAll()); + }); + } +} + +export default new ContactsModel(); diff --git a/ComApp/src/Models/Directories.js b/ComApp/src/Models/Directories.js new file mode 100644 index 0000000..85d7b99 --- /dev/null +++ b/ComApp/src/Models/Directories.js @@ -0,0 +1,104 @@ +import Database from '../Database'; + +const Realm = require('realm'); +let realm = new Realm(Database); + +class DirectoriesModel { + constructor() { + this.schema = 'Directories'; + } + + add(directory) { + realm.write(() => { + realm.create(this.schema, directory); + }); + } + + update(directory) { + realm.write(() => { + realm.create(this.schema, directory, true); + }); + } + + get(id) { + let tmp = realm.objects(this.schema).filtered('id = ' + id)[0]; + return tmp; + } + + getAll(role, authorizedFolders, format, lang) { + if (authorizedFolders !== null && authorizedFolders !== undefined) { + authorizedFolders = JSON.parse(authorizedFolders); + } + let tmp = realm.objects(this.schema); + if (role === 'USER') { + if (authorizedFolders.length > 0) { + tmp = tmp.filtered( + 'needAuthorization = 0 OR ' + + authorizedFolders + .map(folder => ' id = "' + folder.id + '"') + .join(' OR '), + ); + } else { + tmp = tmp.filtered( + 'needAuthorization = 0' + ); + } + } + if (format) { + return this._recursiveDir(tmp, lang); + } + return tmp; + } + + clear() { + realm.write(() => { + realm.delete(this.getAll('SYNC', null, false)); + }); + } + + _recursiveDir(list, lang, parent = null) { + let root = list.filter(e => e.parent === parent); + let tmp = []; + for (let i = 0; i < root.length; i++) { + let childs = this._recursiveDir(list, lang, root[i].id); + let name = JSON.parse(root[i].name); + tmp[i] = {}; + tmp[i].id = root[i].id; + tmp[i].name = this.getI18nName(lang, name); + tmp[i].items = []; + for (let j = 0; j < childs.length; j++) { + childs[j].value = childs[j].id; + tmp[i].items.push(childs[j]); + } + if (!tmp[i].items.length) { + delete tmp[i].items; + } + } + return tmp; + } + + getI18nName(lang, name) { + let tmp = undefined; + switch (lang) { + case "fr": + tmp = name.fr; + break; + case "en": + tmp = name.en; + break; + case "de": + tmp = name.de; + break; + case "es": + tmp = name.es; + break; + } + if (tmp === undefined) { + tmp = name.fr; + } + return tmp; + } + +} + +export default new DirectoriesModel(); diff --git a/ComApp/src/Models/Documents.js b/ComApp/src/Models/Documents.js new file mode 100644 index 0000000..18ce430 --- /dev/null +++ b/ComApp/src/Models/Documents.js @@ -0,0 +1,68 @@ +import Database from '../Database'; + +const Realm = require('realm'); +let realm = new Realm(Database); + +class DocumentsModel { + constructor() { + this.schema = 'Documents'; + } + + add(document) { + realm.write(() => { + realm.create(this.schema, document, 'never'); + }); + } + + update(document, updatedAtChange = false) { + let tmp = this.get(document.id); + if (tmp === undefined) { + this.add(document); + } else { + realm.write(() => { + if (updatedAtChange === false) { + realm.create(this.schema, { + id: tmp.id, + uuid: document.uuid, + title: document.title, + type: document.type, + path: document.path, + directory: document.directory + }, 'modified'); + } else { + realm.create(this.schema, document, 'modified'); + } + }); + } + } + + get(id) { + let tmp = realm.objects(this.schema).filtered('id = ' + id)[0]; + return tmp; + } + + getAll(authorizedDirs = false) { + let tmp = realm.objects(this.schema); + if (authorizedDirs) { + authorizedDirs = authorizedDirs.map(item => item.id); + tmp = tmp.filter(doc => authorizedDirs.includes(doc.directory)); + } + return tmp; + } + + clear(docs, toDelete = []) { + const localDocs = this.getAll(); + localDocs.forEach(doc => { + let tmp = docs.filter(item => item.uuid === doc.uuid); + if (tmp.length === 0) { + toDelete.push({uuid: doc.uuid, type: doc.type}); + realm.write(() => { + realm.delete(doc); + }); + } + }); + return toDelete; + } +} + +export default new DocumentsModel(); diff --git a/ComApp/src/Models/SendBrochure.js b/ComApp/src/Models/SendBrochure.js new file mode 100644 index 0000000..fab3920 --- /dev/null +++ b/ComApp/src/Models/SendBrochure.js @@ -0,0 +1,49 @@ +import Database from '../Database'; + +const Realm = require('realm'); +let realm = new Realm(Database); + +class SendBrochureModel { + constructor() { + this.schema = 'SendBrochure'; + } + + add(data) { + realm.write(() => { + if (data.id === undefined) { + let id: any = realm.objects(this.schema).max('id'); + data.id = id === undefined ? 1 : id + 1; + } + realm.create(this.schema, data); + }); + } + + update(data) { + realm.write(() => { + realm.create(this.schema, data, true); + }); + } + + get(id) { + let tmp = realm.objects(this.schema); + if (tmp.length === 0) { + return false; + } else { + tmp = tmp.filtered('id = ' + id)[0]; + } + return tmp; + } + + getAll() { + let tmp = realm.objects(this.schema); + return tmp; + } + + clear() { + realm.write(() => { + realm.delete(this.getAll()); + }); + } +} + +export default new SendBrochureModel(); diff --git a/ComApp/src/Models/Users.js b/ComApp/src/Models/Users.js new file mode 100644 index 0000000..1d816ed --- /dev/null +++ b/ComApp/src/Models/Users.js @@ -0,0 +1,40 @@ +import Database from '../Database'; + +const Realm = require('realm'); +let realm = new Realm(Database); + +class UsersModel { + constructor() { + this.schema = 'Users'; + } + + add(user) { + realm.write(() => { + realm.create(this.schema, user); + }); + } + + update(user) { + realm.write(() => { + realm.create(this.schema, user, true); + }); + } + + get(id) { + let tmp = realm.objects(this.schema).filtered('id = ' + id)[0]; + return tmp; + } + + getAll() { + let tmp = realm.objects(this.schema); + return tmp; + } + + clear() { + realm.write(() => { + realm.delete(this.getAll()); + }); + } +} + +export default new UsersModel(); diff --git a/ComApp/src/Views/Contact.js b/ComApp/src/Views/Contact.js new file mode 100644 index 0000000..aa62230 --- /dev/null +++ b/ComApp/src/Views/Contact.js @@ -0,0 +1,606 @@ +import React from 'react'; +import { + View, + StyleSheet, + Button, + FlatList, + Text, + Dimensions, + TouchableOpacity, + Alert, + TextInput, +} from 'react-native'; +import Icon from 'react-native-vector-icons/FontAwesome'; +import { connect, ReactReduxContext } from 'react-redux'; +import { showMessage } from 'react-native-flash-message'; +import { Overlay } from '@rneui/themed'; +import Helpers from "../Controllers/Helpers"; +import i18n from "../I18n"; +import { Picker } from '@react-native-picker/picker'; + +function mapStateToProps(state) { + return { + users: state.users, + contacts: state.contacts, + documents: state.documents, + currentUser: state.currentUser, + }; +} + +class ContactScreen extends React.Component { + state = { + contacts: [], + sortType: 'asc', + sortField: 'name', + searchTxt: '', + showTransferContact: false, + selectedTransferUser: 0, + contactToTransfer: {}, + showSendBrochure: false, + selectedDocument: -1, + sendDocumentList: [], + selectedSendBrochureEmail: -1, + tmpSendBrochureEmail: '', + tmpSendBrochureSubject: '', + tmpSendBrochureMessage: '', + sortedDocuments: [], + sortedEmails: [], + }; + + sendBrochure() { + if ( + this.state.tmpSendBrochureEmail !== '' && + this.state.tmpSendBrochureSubject !== '' && + this.state.tmpSendBrochureMessage !== '' && + this.state.sendDocumentList.length > 0 && + Helpers.isEmailValid(this.state.tmpSendBrochureEmail) + ) { + const email = { + email: this.state.tmpSendBrochureEmail, + subject: this.state.tmpSendBrochureSubject, + message: this.state.tmpSendBrochureMessage, + documents: JSON.stringify(this.state.sendDocumentList), + user_id: this.props.currentUser.id + }; + this.props.dispatch({ type: 'SEND_BROCHURE', data: 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() { + const nextUser = this.props.users.filter(user => user.id !== this.props.currentUser.id)[this.state.selectedTransferUser]; + const transferContact = this.state.contactToTransfer; + const contact = { + id: transferContact.id, + email: transferContact.email, + type: transferContact.type, + other: transferContact.other, + network: transferContact.network, + name: transferContact.name, + firstname: transferContact.firstname, + office: transferContact.office, + company: transferContact.company, + address: transferContact.address, + phone: transferContact.phone, + products: transferContact.products, + notes: transferContact.note, + visits: transferContact.visits, + to_delete: false, + synced: false, + user_id: nextUser.id, + }; + this.props.dispatch({ type: 'UPDATE_CONTACT', data: contact }); + this.setState({ showTransferContact: false }); + showMessage({ + message: i18n.t('contact_s.contact_transferred') + '.', + description: i18n.t('contact_s.contact_will_be_sent_after_sync') + '.', + type: 'success', + duration: 1500, + }); + } + + deleteContact(id) { + Alert.alert( + i18n.t('global.confirmation'), + i18n.t('contact_s.confirm_contact_delete'), + [ + { + text: i18n.t('global.no'), + onPress: () => console.log('Cancel Pressed'), + style: 'cancel', + }, + { + text: i18n.t('global.yes'), + onPress: () => { + this.setState({ contacts: [], sortedEmails: [] }); + this.props.dispatch({ type: 'DELETE_CONTACT', data: id }); + showMessage({ + message: i18n.t('contact_s.contact_deleted') + '.', + type: 'success', + duration: 500, + }); + }, + }, + ], + { cancelable: false }, + ); + } + + Item(item) { + return ( + + + this.props.navigation.navigate('ContactDetailsScreen', { + action: 'edit', + contactId: item.id, + }) + }> + + {item.name.toUpperCase()} {item.firstname} + + + {item.company} + {item.email} + + { + this.deleteContact(item.id); + }}> + + + { + this.setState({ + showTransferContact: true, + contactToTransfer: item, + }); + }}> + + + + + ); + } + + 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 }); + } + + HeaderItem() { + return ( + + { + this.sortContacts('name'); + }}> + {i18n.t('global.name')} + + { + this.sortContacts('company'); + }}> + {i18n.t('global.company')} + + {i18n.t('global.email')} + + + ); + } + + 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 }); + if (text.length > 0) { + let tmpContactsName = this.props.contacts.filter(item => item.name.toLowerCase().indexOf(text.toLowerCase()) >= 0); + let tmpContactsCompany = this.props.contacts.filter(item => item.company.toLowerCase().indexOf(text.toLowerCase()) >= 0); + let resSearch = Object.assign(tmpContactsName, tmpContactsCompany); + this.setState({ contacts: resSearch.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]; }); + + this.setState({ + contacts: arrayContacts.sort(Helpers.compareValues('name', 'asc')), + 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 ( + + +