Merge branch 'master' into 82-chart-improvements
This commit is contained in:
@@ -139,6 +139,8 @@ android {
|
|||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
compile project(':react-native-vector-icons')
|
compile project(':react-native-vector-icons')
|
||||||
|
compile project(':react-native-fs')
|
||||||
|
compile project(':react-native-document-picker')
|
||||||
compile project(':react-native-share')
|
compile project(':react-native-share')
|
||||||
compile project(':realm')
|
compile project(':realm')
|
||||||
compile fileTree(dir: "libs", include: ["*.jar"])
|
compile fileTree(dir: "libs", include: ["*.jar"])
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import android.app.Application;
|
|||||||
|
|
||||||
import com.facebook.react.ReactApplication;
|
import com.facebook.react.ReactApplication;
|
||||||
import com.oblador.vectoricons.VectorIconsPackage;
|
import com.oblador.vectoricons.VectorIconsPackage;
|
||||||
|
import com.rnfs.RNFSPackage;
|
||||||
|
import com.reactnativedocumentpicker.ReactNativeDocumentPicker;
|
||||||
import cl.json.RNSharePackage;
|
import cl.json.RNSharePackage;
|
||||||
import cl.json.ShareApplication;
|
import cl.json.ShareApplication;
|
||||||
import io.realm.react.RealmReactPackage;
|
import io.realm.react.RealmReactPackage;
|
||||||
@@ -28,6 +30,8 @@ public class MainApplication extends Application implements ReactApplication, Sh
|
|||||||
return Arrays.<ReactPackage>asList(
|
return Arrays.<ReactPackage>asList(
|
||||||
new MainReactPackage(),
|
new MainReactPackage(),
|
||||||
new VectorIconsPackage(),
|
new VectorIconsPackage(),
|
||||||
|
new RNFSPackage(),
|
||||||
|
new ReactNativeDocumentPicker(),
|
||||||
new RNSharePackage(),
|
new RNSharePackage(),
|
||||||
new RealmReactPackage()
|
new RealmReactPackage()
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
rootProject.name = 'drip'
|
rootProject.name = 'drip'
|
||||||
include ':react-native-vector-icons'
|
include ':react-native-vector-icons'
|
||||||
project(':react-native-vector-icons').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-vector-icons/android')
|
project(':react-native-vector-icons').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-vector-icons/android')
|
||||||
|
include ':react-native-fs'
|
||||||
|
project(':react-native-fs').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-fs/android')
|
||||||
|
include ':react-native-document-picker'
|
||||||
|
project(':react-native-document-picker').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-document-picker/android')
|
||||||
include ':react-native-share'
|
include ':react-native-share'
|
||||||
project(':react-native-share').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-share/android')
|
project(':react-native-share').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-share/android')
|
||||||
include ':realm'
|
include ':realm'
|
||||||
|
|||||||
@@ -98,11 +98,20 @@ export default class DayView extends Component {
|
|||||||
<Text style={styles.symptomDayView}>Desire</Text>
|
<Text style={styles.symptomDayView}>Desire</Text>
|
||||||
<View style={styles.symptomEditButton}>
|
<View style={styles.symptomEditButton}>
|
||||||
<Button
|
<Button
|
||||||
onPress={() => this.showView('desireEditView')}
|
onPress={() => this.showView('DesireEditView')}
|
||||||
title={getLabel('desire', cycleDay.desire)}>
|
title={getLabel('desire', cycleDay.desire)}>
|
||||||
</Button>
|
</Button>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
<View style={styles.symptomViewRowInline}>
|
||||||
|
<Text style={styles.symptomDayView}>Sex</Text>
|
||||||
|
<View style={styles.symptomEditButton}>
|
||||||
|
<Button
|
||||||
|
onPress={() => this.showView('SexEditView')}
|
||||||
|
title={getLabel('sex', cycleDay.sex)}>
|
||||||
|
</Button>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
</View >
|
</View >
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -132,15 +141,22 @@ function getLabel(symptomName, symptom) {
|
|||||||
typeof mucus.texture === 'number' &&
|
typeof mucus.texture === 'number' &&
|
||||||
typeof mucus.value === 'number'
|
typeof mucus.value === 'number'
|
||||||
) {
|
) {
|
||||||
let mucusLabel = `${feelingLabels[mucus.feeling]} + ${textureLabels[mucus.texture]} ( ${computeSensiplanMucusLabels[mucus.value]} )`
|
let mucusLabel =
|
||||||
|
`${feelingLabels[mucus.feeling]} +
|
||||||
|
${textureLabels[mucus.texture]}
|
||||||
|
( ${computeSensiplanMucusLabels[mucus.value]} )`
|
||||||
if (mucus.exclude) mucusLabel = "( " + mucusLabel + " )"
|
if (mucus.exclude) mucusLabel = "( " + mucusLabel + " )"
|
||||||
return mucusLabel
|
return mucusLabel
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
cervix: cervix => {
|
cervix: cervix => {
|
||||||
if (cervix.opening > -1 && cervix.firmness > -1) {
|
if (cervix.opening > -1 && cervix.firmness > -1) {
|
||||||
let cervixLabel = `${openingLabels[cervix.opening]} + ${firmnessLabels[cervix.firmness]}`
|
let cervixLabel =
|
||||||
if (cervix.position > -1) cervixLabel += `+ ${positionLabels[cervix.position]}`
|
`${openingLabels[cervix.opening]} +
|
||||||
|
${firmnessLabels[cervix.firmness]}`
|
||||||
|
if (cervix.position > -1) {
|
||||||
|
cervixLabel += `+ ${positionLabels[cervix.position]}`
|
||||||
|
}
|
||||||
if (cervix.exclude) cervixLabel = "( " + cervixLabel + " )"
|
if (cervix.exclude) cervixLabel = "( " + cervixLabel + " )"
|
||||||
return cervixLabel
|
return cervixLabel
|
||||||
}
|
}
|
||||||
@@ -153,6 +169,17 @@ function getLabel(symptomName, symptom) {
|
|||||||
const desireLabel = `${intensityLabels[desire.value]}`
|
const desireLabel = `${intensityLabels[desire.value]}`
|
||||||
return desireLabel
|
return desireLabel
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
sex: sex => {
|
||||||
|
let sexLabel = ''
|
||||||
|
if ( sex.solo || sex.partner ) {
|
||||||
|
sexLabel += 'Activity '
|
||||||
|
}
|
||||||
|
if (sex.condom || sex.pill || sex.iud ||
|
||||||
|
sex.patch || sex.ring || sex.implant || sex.other) {
|
||||||
|
sexLabel += 'Contraceptive'
|
||||||
|
}
|
||||||
|
return sexLabel ? sexLabel : 'edit'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,19 @@ export const cervixOpening = ['closed', 'medium', 'open']
|
|||||||
export const cervixFirmness = ['hard', 'soft']
|
export const cervixFirmness = ['hard', 'soft']
|
||||||
export const cervixPosition = ['low', 'medium', 'high']
|
export const cervixPosition = ['low', 'medium', 'high']
|
||||||
export const intensity = ['low', 'medium', 'high']
|
export const intensity = ['low', 'medium', 'high']
|
||||||
|
export const sexActivity = {
|
||||||
|
solo: 'Solo',
|
||||||
|
partner: 'Partner'
|
||||||
|
}
|
||||||
|
export const contraceptives = {
|
||||||
|
condom: 'Condom',
|
||||||
|
pill: 'Pill',
|
||||||
|
iud: 'IUD',
|
||||||
|
patch: 'Patch',
|
||||||
|
ring: 'Ring',
|
||||||
|
implant: 'Implant',
|
||||||
|
other: 'Other'
|
||||||
|
}
|
||||||
|
|
||||||
export const fertilityStatus = {
|
export const fertilityStatus = {
|
||||||
fertile: 'fertile',
|
fertile: 'fertile',
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import MucusEditView from './mucus'
|
|||||||
import CervixEditView from './cervix'
|
import CervixEditView from './cervix'
|
||||||
import NoteEditView from './note'
|
import NoteEditView from './note'
|
||||||
import DesireEditView from './desire'
|
import DesireEditView from './desire'
|
||||||
|
import SexEditView from './sex'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
BleedingEditView,
|
BleedingEditView,
|
||||||
@@ -11,5 +12,6 @@ export default {
|
|||||||
MucusEditView,
|
MucusEditView,
|
||||||
CervixEditView,
|
CervixEditView,
|
||||||
NoteEditView,
|
NoteEditView,
|
||||||
DesireEditView
|
DesireEditView,
|
||||||
|
SexEditView
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,156 @@
|
|||||||
|
import React, { Component } from 'react'
|
||||||
|
import {
|
||||||
|
CheckBox,
|
||||||
|
Text,
|
||||||
|
TextInput,
|
||||||
|
View
|
||||||
|
} from 'react-native'
|
||||||
|
import styles from '../../../styles'
|
||||||
|
import { saveSymptom } from '../../../db'
|
||||||
|
import {
|
||||||
|
sexActivity as activityLabels,
|
||||||
|
contraceptives as contraceptiveLabels
|
||||||
|
} from '../labels/labels'
|
||||||
|
|
||||||
|
export default class Sex extends Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props)
|
||||||
|
this.cycleDay = props.cycleDay
|
||||||
|
this.state = {}
|
||||||
|
if (this.cycleDay.sex !== null ) {
|
||||||
|
Object.assign(this.state, this.cycleDay.sex)
|
||||||
|
// We make sure other is always true when there is a note,
|
||||||
|
// e.g. when import is messed up.
|
||||||
|
if (this.cycleDay.sex && this.cycleDay.sex.note) {
|
||||||
|
this.state.other = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={styles.symptomEditView}>
|
||||||
|
<Text style={styles.symptomDayView}>SEX</Text>
|
||||||
|
<View style={styles.symptomViewRowInline}>
|
||||||
|
<Text style={styles.symptomDayView}>{activityLabels.solo}</Text>
|
||||||
|
<CheckBox
|
||||||
|
value={this.state.solo}
|
||||||
|
onValueChange={(val) => {
|
||||||
|
this.setState({solo: val})
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Text style={styles.symptomDayView}>{activityLabels.partner}</Text>
|
||||||
|
<CheckBox
|
||||||
|
value={this.state.partner}
|
||||||
|
onValueChange={(val) => {
|
||||||
|
this.setState({partner: val})
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
<Text style={styles.symptomDayView}>CONTRACEPTIVES</Text>
|
||||||
|
<View style={styles.symptomViewRowInline}>
|
||||||
|
<Text style={styles.symptomDayView}>
|
||||||
|
{contraceptiveLabels.condom}
|
||||||
|
</Text>
|
||||||
|
<CheckBox
|
||||||
|
value={this.state.condom}
|
||||||
|
onValueChange={(val) => {
|
||||||
|
this.setState({condom: val})
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Text style={styles.symptomDayView}>
|
||||||
|
{contraceptiveLabels.pill}
|
||||||
|
</Text>
|
||||||
|
<CheckBox
|
||||||
|
value={this.state.pill}
|
||||||
|
onValueChange={(val) => {
|
||||||
|
this.setState({pill: val})
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
<View style={styles.symptomViewRowInline}>
|
||||||
|
<Text style={styles.symptomDayView}>
|
||||||
|
{contraceptiveLabels.iud}
|
||||||
|
</Text>
|
||||||
|
<CheckBox
|
||||||
|
value={this.state.iud}
|
||||||
|
onValueChange={(val) => {
|
||||||
|
this.setState({iud: val})
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Text style={styles.symptomDayView}>
|
||||||
|
{contraceptiveLabels.patch}
|
||||||
|
</Text>
|
||||||
|
<CheckBox
|
||||||
|
value={this.state.patch}
|
||||||
|
onValueChange={(val) => {
|
||||||
|
this.setState({patch: val})
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
<View style={styles.symptomViewRowInline}>
|
||||||
|
<Text style={styles.symptomDayView}>
|
||||||
|
{contraceptiveLabels.ring}
|
||||||
|
</Text>
|
||||||
|
<CheckBox
|
||||||
|
value={this.state.ring}
|
||||||
|
onValueChange={(val) => {
|
||||||
|
this.setState({ring: val})
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Text style={styles.symptomDayView}>
|
||||||
|
{contraceptiveLabels.implant}
|
||||||
|
</Text>
|
||||||
|
<CheckBox
|
||||||
|
value={this.state.implant}
|
||||||
|
onValueChange={(val) => {
|
||||||
|
this.setState({implant: val})
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
<View style={styles.symptomViewRowInline}>
|
||||||
|
<Text style={styles.symptomDayView}>
|
||||||
|
{contraceptiveLabels.other}
|
||||||
|
</Text>
|
||||||
|
<CheckBox
|
||||||
|
value={this.state.other}
|
||||||
|
onValueChange={(val) => {
|
||||||
|
this.setState({
|
||||||
|
other: val,
|
||||||
|
focusTextArea: true
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
{ this.state.other &&
|
||||||
|
<TextInput
|
||||||
|
autoFocus={this.state.focusTextArea}
|
||||||
|
multiline={true}
|
||||||
|
placeholder="Enter"
|
||||||
|
value={this.state.note}
|
||||||
|
onChangeText={(val) => {
|
||||||
|
this.setState({note: val})
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
<View style={styles.actionButtonRow}>
|
||||||
|
{this.props.makeActionButtons(
|
||||||
|
{
|
||||||
|
symptom: 'sex',
|
||||||
|
cycleDay: this.cycleDay,
|
||||||
|
saveAction: () => {
|
||||||
|
const copyOfState = Object.assign({}, this.state)
|
||||||
|
if (!copyOfState.other) {
|
||||||
|
copyOfState.note = null
|
||||||
|
}
|
||||||
|
saveSymptom('sex', this.cycleDay, copyOfState)
|
||||||
|
},
|
||||||
|
saveDisabled: Object.values(this.state).every(value => !value)
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
+30
-7
@@ -1,10 +1,33 @@
|
|||||||
export const settings = {
|
export const settings = {
|
||||||
errors: {
|
shared: {
|
||||||
noData: 'There is no data to export',
|
cancel: 'Cancel',
|
||||||
couldNotConvert: 'Could not convert data to CSV',
|
errorTitle: 'Error',
|
||||||
problemSharing: 'There was a problem sharing the data export file'
|
successTitle: 'Success'
|
||||||
},
|
},
|
||||||
exportTitle: 'My Drip data export',
|
export: {
|
||||||
exportSubject: 'My Drip data export',
|
errors: {
|
||||||
buttonLabel: 'Export data'
|
noData: 'There is no data to export',
|
||||||
|
couldNotConvert: 'Could not convert data to CSV',
|
||||||
|
problemSharing: 'There was a problem sharing the data export file'
|
||||||
|
},
|
||||||
|
title: 'My Drip data export',
|
||||||
|
subject: 'My Drip data export',
|
||||||
|
button: 'Export data',
|
||||||
|
},
|
||||||
|
import: {
|
||||||
|
button: 'Import data',
|
||||||
|
title: 'Keep existing data?',
|
||||||
|
message: `There are two options for the import:
|
||||||
|
1. Keep existing cycle days and replace only the ones in the import file.
|
||||||
|
2. Delete all existing cycle days and import cycle days from file.`,
|
||||||
|
replaceOption: 'Import and replace',
|
||||||
|
deleteOption: 'Import and delete existing',
|
||||||
|
errors: {
|
||||||
|
couldNotOpenFile: 'Could not open file',
|
||||||
|
postFix: 'No data was imported or changed'
|
||||||
|
},
|
||||||
|
success: {
|
||||||
|
message: 'Data successfully imported'
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
+94
-27
@@ -7,9 +7,12 @@ import {
|
|||||||
} from 'react-native'
|
} from 'react-native'
|
||||||
|
|
||||||
import Share from 'react-native-share'
|
import Share from 'react-native-share'
|
||||||
import getDataAsCsvDataUri from '../lib/export-to-csv'
|
import { DocumentPicker, DocumentPickerUtil } from 'react-native-document-picker'
|
||||||
|
import rnfs from 'react-native-fs'
|
||||||
import styles from '../styles/index'
|
import styles from '../styles/index'
|
||||||
import { settings as labels } from './labels'
|
import { settings as labels } from './labels'
|
||||||
|
import getDataAsCsvDataUri from '../lib/import-export/export-to-csv'
|
||||||
|
import importCsv from '../lib/import-export/import-from-csv'
|
||||||
|
|
||||||
export default class Settings extends Component {
|
export default class Settings extends Component {
|
||||||
render() {
|
render() {
|
||||||
@@ -18,32 +21,14 @@ export default class Settings extends Component {
|
|||||||
<View style={styles.homeButtons}>
|
<View style={styles.homeButtons}>
|
||||||
<View style={styles.homeButton}>
|
<View style={styles.homeButton}>
|
||||||
<Button
|
<Button
|
||||||
onPress={async () => {
|
onPress={ openShareDialogAndExport }
|
||||||
let data
|
title={labels.export.button}>
|
||||||
try {
|
</Button>
|
||||||
data = getDataAsCsvDataUri()
|
</View>
|
||||||
if (!data) {
|
<View style={styles.homeButton}>
|
||||||
return Alert.alert(labels.errors.noData)
|
<Button
|
||||||
}
|
title={labels.import.button}
|
||||||
} catch (err) {
|
onPress={ openImportDialogAndImport }>
|
||||||
console.error(err)
|
|
||||||
return Alert.alert(labels.errors.couldNotConvert)
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
await Share.open({
|
|
||||||
title: labels.exportTitle,
|
|
||||||
url: data,
|
|
||||||
subject: labels.exportSubject,
|
|
||||||
type: 'text/csv',
|
|
||||||
showAppsToView: true
|
|
||||||
})
|
|
||||||
} catch (err) {
|
|
||||||
console.error(err)
|
|
||||||
return Alert.alert(labels.errors.problemSharing)
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
title={labels.buttonLabel}>
|
|
||||||
</Button>
|
</Button>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
@@ -51,3 +36,85 @@ export default class Settings extends Component {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function openShareDialogAndExport() {
|
||||||
|
let data
|
||||||
|
try {
|
||||||
|
data = getDataAsCsvDataUri()
|
||||||
|
if (!data) {
|
||||||
|
return alertError(labels.errors.noData)
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err)
|
||||||
|
return alertError(labels.errors.couldNotConvert)
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await Share.open({
|
||||||
|
title: labels.export.title,
|
||||||
|
url: data,
|
||||||
|
subject: labels.export.subject,
|
||||||
|
type: 'text/csv',
|
||||||
|
showAppsToView: true
|
||||||
|
})
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err)
|
||||||
|
return alertError(labels.export.errors.problemSharing)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function openImportDialogAndImport() {
|
||||||
|
Alert.alert(
|
||||||
|
labels.import.title,
|
||||||
|
labels.import.message,
|
||||||
|
[{
|
||||||
|
text: labels.import.replaceOption,
|
||||||
|
onPress: () => getFileContentAndImport({ deleteExisting: false })
|
||||||
|
}, {
|
||||||
|
text: labels.import.deleteOption,
|
||||||
|
onPress: () => getFileContentAndImport({ deleteExisting: true })
|
||||||
|
}, {
|
||||||
|
text: labels.shared.cancel, style: 'cancel', onPress: () => { }
|
||||||
|
}]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getFileContentAndImport({ deleteExisting }) {
|
||||||
|
let fileInfo
|
||||||
|
try {
|
||||||
|
fileInfo = await new Promise((resolve, reject) => {
|
||||||
|
DocumentPicker.show({
|
||||||
|
filetype: [DocumentPickerUtil.allFiles()],
|
||||||
|
}, (err, res) => {
|
||||||
|
if (err) return reject(err)
|
||||||
|
resolve(res)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
} catch (err) {
|
||||||
|
// because cancelling also triggers an error, we do nothing here
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let fileContent
|
||||||
|
try {
|
||||||
|
fileContent = await rnfs.readFile(fileInfo.uri, 'utf8')
|
||||||
|
} catch (err) {
|
||||||
|
return importError(labels.import.errors.couldNotOpenFile)
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await importCsv(fileContent, deleteExisting)
|
||||||
|
Alert.alert(labels.import.success.title, labels.import.success.message)
|
||||||
|
} catch(err) {
|
||||||
|
importError(err.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function alertError(msg) {
|
||||||
|
Alert.alert(labels.shared.errorTitle, msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
function importError(msg) {
|
||||||
|
const postFixed = `${msg}\n\n${labels.import.errors.postFix}`
|
||||||
|
alertError(postFixed)
|
||||||
|
}
|
||||||
+53
-17
@@ -60,6 +60,22 @@ const DesireSchema = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const SexSchema = {
|
||||||
|
name: 'Sex',
|
||||||
|
properties: {
|
||||||
|
solo: { type: 'bool', optional: true },
|
||||||
|
partner: { type: 'bool', optional: true },
|
||||||
|
condom: { type: 'bool', optional: true },
|
||||||
|
pill: { type: 'bool', optional: true },
|
||||||
|
iud: { type: 'bool', optional: true },
|
||||||
|
patch: { type: 'bool', optional: true },
|
||||||
|
ring: { type: 'bool', optional: true },
|
||||||
|
implant: { type: 'bool', optional: true },
|
||||||
|
other: { type: 'bool', optional: true },
|
||||||
|
note: { type: 'string', optional: true }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const CycleDaySchema = {
|
const CycleDaySchema = {
|
||||||
name: 'CycleDay',
|
name: 'CycleDay',
|
||||||
primaryKey: 'date',
|
primaryKey: 'date',
|
||||||
@@ -88,6 +104,10 @@ const CycleDaySchema = {
|
|||||||
desire: {
|
desire: {
|
||||||
type: 'Desire',
|
type: 'Desire',
|
||||||
optional: true
|
optional: true
|
||||||
|
},
|
||||||
|
sex: {
|
||||||
|
type: 'Sex',
|
||||||
|
optional: true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -100,7 +120,8 @@ const realmConfig = {
|
|||||||
MucusSchema,
|
MucusSchema,
|
||||||
CervixSchema,
|
CervixSchema,
|
||||||
NoteSchema,
|
NoteSchema,
|
||||||
DesireSchema
|
DesireSchema,
|
||||||
|
SexSchema
|
||||||
],
|
],
|
||||||
// we only want this in dev mode
|
// we only want this in dev mode
|
||||||
deleteRealmIfMigrationNeeded: true
|
deleteRealmIfMigrationNeeded: true
|
||||||
@@ -175,21 +196,17 @@ function getPreviousTemperature(cycleDay) {
|
|||||||
return winner.temperature.value
|
return winner.temperature.value
|
||||||
}
|
}
|
||||||
|
|
||||||
function getColumnNamesForCsv() {
|
const schema = db.schema.reduce((acc, curr) => {
|
||||||
return getPrefixedKeys('CycleDay')
|
acc[curr.name] = curr.properties
|
||||||
|
return acc
|
||||||
|
}, {})
|
||||||
|
|
||||||
function getPrefixedKeys(schemaName, prefix) {
|
function tryToCreateCycleDay(day, i) {
|
||||||
const schema = db.schema.find(x => x.name === schemaName).properties
|
try {
|
||||||
return Object.keys(schema).reduce((acc, key) => {
|
db.create('CycleDay', day)
|
||||||
const prefixedKey = prefix ? [prefix, key].join('.') : key
|
} catch (err) {
|
||||||
const childSchemaName = schema[key].objectType
|
const msg = `Line ${i + 1}(${day.date}): ${err.message}`
|
||||||
if (!childSchemaName) {
|
throw new Error(msg)
|
||||||
acc.push(prefixedKey)
|
|
||||||
return acc
|
|
||||||
}
|
|
||||||
acc.push(...getPrefixedKeys(childSchemaName, prefixedKey))
|
|
||||||
return acc
|
|
||||||
}, [])
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -202,6 +219,23 @@ function getAmountOfCycleDays() {
|
|||||||
return earliestAsLocalDate.until(today, ChronoUnit.DAYS)
|
return earliestAsLocalDate.until(today, ChronoUnit.DAYS)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function tryToImportWithDelete(cycleDays) {
|
||||||
|
db.write(() => {
|
||||||
|
db.delete(db.objects('CycleDay'))
|
||||||
|
cycleDays.forEach(tryToCreateCycleDay)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function tryToImportWithoutDelete(cycleDays) {
|
||||||
|
db.write(() => {
|
||||||
|
cycleDays.forEach((day, i) => {
|
||||||
|
const existing = getCycleDay(day.date)
|
||||||
|
if (existing) db.delete(existing)
|
||||||
|
tryToCreateCycleDay(day, i)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
saveSymptom,
|
saveSymptom,
|
||||||
getOrCreateCycleDay,
|
getOrCreateCycleDay,
|
||||||
@@ -212,6 +246,8 @@ export {
|
|||||||
deleteAll,
|
deleteAll,
|
||||||
getPreviousTemperature,
|
getPreviousTemperature,
|
||||||
getCycleDay,
|
getCycleDay,
|
||||||
getColumnNamesForCsv,
|
getAmountOfCycleDays,
|
||||||
getAmountOfCycleDays
|
schema,
|
||||||
|
tryToImportWithDelete,
|
||||||
|
tryToImportWithoutDelete
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,6 +56,8 @@
|
|||||||
DB91E6CCC3EB4A549D947797 /* Octicons.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 4902D5DCD46748BD8DC403FD /* Octicons.ttf */; };
|
DB91E6CCC3EB4A549D947797 /* Octicons.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 4902D5DCD46748BD8DC403FD /* Octicons.ttf */; };
|
||||||
3DF2498A20844F298CD84CC3 /* SimpleLineIcons.ttf in Resources */ = {isa = PBXBuildFile; fileRef = E954835D62BD45F0A5FFC523 /* SimpleLineIcons.ttf */; };
|
3DF2498A20844F298CD84CC3 /* SimpleLineIcons.ttf in Resources */ = {isa = PBXBuildFile; fileRef = E954835D62BD45F0A5FFC523 /* SimpleLineIcons.ttf */; };
|
||||||
A1410AC4C98A49B2820D9E45 /* Zocial.ttf in Resources */ = {isa = PBXBuildFile; fileRef = B6F5078F7DEC470782757471 /* Zocial.ttf */; };
|
A1410AC4C98A49B2820D9E45 /* Zocial.ttf in Resources */ = {isa = PBXBuildFile; fileRef = B6F5078F7DEC470782757471 /* Zocial.ttf */; };
|
||||||
|
29DF0CCC1AEA4C92BCA0BCCD /* libRNDocumentPicker.a in Frameworks */ = {isa = PBXBuildFile; fileRef = D211D71BE5A8436A978770A9 /* libRNDocumentPicker.a */; };
|
||||||
|
17AD822C42A44BADA96BD860 /* libRNFS.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 84CCEBD3B2C44758853BC941 /* libRNFS.a */; };
|
||||||
/* End PBXBuildFile section */
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
/* Begin PBXContainerItemProxy section */
|
/* Begin PBXContainerItemProxy section */
|
||||||
@@ -383,6 +385,10 @@
|
|||||||
4902D5DCD46748BD8DC403FD /* Octicons.ttf */ = {isa = PBXFileReference; name = "Octicons.ttf"; path = "../node_modules/react-native-vector-icons/Fonts/Octicons.ttf"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; };
|
4902D5DCD46748BD8DC403FD /* Octicons.ttf */ = {isa = PBXFileReference; name = "Octicons.ttf"; path = "../node_modules/react-native-vector-icons/Fonts/Octicons.ttf"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; };
|
||||||
E954835D62BD45F0A5FFC523 /* SimpleLineIcons.ttf */ = {isa = PBXFileReference; name = "SimpleLineIcons.ttf"; path = "../node_modules/react-native-vector-icons/Fonts/SimpleLineIcons.ttf"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; };
|
E954835D62BD45F0A5FFC523 /* SimpleLineIcons.ttf */ = {isa = PBXFileReference; name = "SimpleLineIcons.ttf"; path = "../node_modules/react-native-vector-icons/Fonts/SimpleLineIcons.ttf"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; };
|
||||||
B6F5078F7DEC470782757471 /* Zocial.ttf */ = {isa = PBXFileReference; name = "Zocial.ttf"; path = "../node_modules/react-native-vector-icons/Fonts/Zocial.ttf"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; };
|
B6F5078F7DEC470782757471 /* Zocial.ttf */ = {isa = PBXFileReference; name = "Zocial.ttf"; path = "../node_modules/react-native-vector-icons/Fonts/Zocial.ttf"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; };
|
||||||
|
1F05FE29622E4F21AF70C2B7 /* RNDocumentPicker.xcodeproj */ = {isa = PBXFileReference; name = "RNDocumentPicker.xcodeproj"; path = "../node_modules/react-native-document-picker/ios/RNDocumentPicker.xcodeproj"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = wrapper.pb-project; explicitFileType = undefined; includeInIndex = 0; };
|
||||||
|
D211D71BE5A8436A978770A9 /* libRNDocumentPicker.a */ = {isa = PBXFileReference; name = "libRNDocumentPicker.a"; path = "libRNDocumentPicker.a"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = archive.ar; explicitFileType = undefined; includeInIndex = 0; };
|
||||||
|
49089E09BFCF4F3DB209B6E9 /* RNFS.xcodeproj */ = {isa = PBXFileReference; name = "RNFS.xcodeproj"; path = "../node_modules/react-native-fs/RNFS.xcodeproj"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = wrapper.pb-project; explicitFileType = undefined; includeInIndex = 0; };
|
||||||
|
84CCEBD3B2C44758853BC941 /* libRNFS.a */ = {isa = PBXFileReference; name = "libRNFS.a"; path = "libRNFS.a"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = archive.ar; explicitFileType = undefined; includeInIndex = 0; };
|
||||||
/* End PBXFileReference section */
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
/* Begin PBXFrameworksBuildPhase section */
|
/* Begin PBXFrameworksBuildPhase section */
|
||||||
@@ -416,6 +422,8 @@
|
|||||||
D91133DCE120440893E2FD2E /* libz.tbd in Frameworks */,
|
D91133DCE120440893E2FD2E /* libz.tbd in Frameworks */,
|
||||||
26DC04B498C64CE5AAA0C4F8 /* libRNShare.a in Frameworks */,
|
26DC04B498C64CE5AAA0C4F8 /* libRNShare.a in Frameworks */,
|
||||||
AED64B7892744F21B3A156BB /* libRNVectorIcons.a in Frameworks */,
|
AED64B7892744F21B3A156BB /* libRNVectorIcons.a in Frameworks */,
|
||||||
|
29DF0CCC1AEA4C92BCA0BCCD /* libRNDocumentPicker.a in Frameworks */,
|
||||||
|
17AD822C42A44BADA96BD860 /* libRNFS.a in Frameworks */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
@@ -608,6 +616,8 @@
|
|||||||
7F6C9FA9B66B453CA602B334 /* RealmReact.xcodeproj */,
|
7F6C9FA9B66B453CA602B334 /* RealmReact.xcodeproj */,
|
||||||
4E6AB77B55F2491487B6124E /* RNShare.xcodeproj */,
|
4E6AB77B55F2491487B6124E /* RNShare.xcodeproj */,
|
||||||
D1E5ACC4B66345868F556374 /* RNVectorIcons.xcodeproj */,
|
D1E5ACC4B66345868F556374 /* RNVectorIcons.xcodeproj */,
|
||||||
|
1F05FE29622E4F21AF70C2B7 /* RNDocumentPicker.xcodeproj */,
|
||||||
|
49089E09BFCF4F3DB209B6E9 /* RNFS.xcodeproj */,
|
||||||
);
|
);
|
||||||
name = Libraries;
|
name = Libraries;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -1272,12 +1282,16 @@
|
|||||||
LIBRARY_SEARCH_PATHS = (
|
LIBRARY_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"\"$(SRCROOT)/$(TARGET_NAME)\"",
|
"\"$(SRCROOT)/$(TARGET_NAME)\"",
|
||||||
|
"\"$(SRCROOT)/$(TARGET_NAME)\"",
|
||||||
|
"\"$(SRCROOT)/$(TARGET_NAME)\"",
|
||||||
);
|
);
|
||||||
HEADER_SEARCH_PATHS = (
|
HEADER_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"$(SRCROOT)/../node_modules/realm/src/**",
|
"$(SRCROOT)/../node_modules/realm/src/**",
|
||||||
"$(SRCROOT)/../node_modules/react-native-share/ios",
|
"$(SRCROOT)/../node_modules/react-native-share/ios",
|
||||||
"$(SRCROOT)/../node_modules/react-native-vector-icons/RNVectorIconsManager",
|
"$(SRCROOT)/../node_modules/react-native-vector-icons/RNVectorIconsManager",
|
||||||
|
"$(SRCROOT)/../node_modules/react-native-document-picker/ios/RNDocumentPicker",
|
||||||
|
"$(SRCROOT)/../node_modules/react-native-fs/**",
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
name = Debug;
|
name = Debug;
|
||||||
@@ -1299,12 +1313,16 @@
|
|||||||
LIBRARY_SEARCH_PATHS = (
|
LIBRARY_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"\"$(SRCROOT)/$(TARGET_NAME)\"",
|
"\"$(SRCROOT)/$(TARGET_NAME)\"",
|
||||||
|
"\"$(SRCROOT)/$(TARGET_NAME)\"",
|
||||||
|
"\"$(SRCROOT)/$(TARGET_NAME)\"",
|
||||||
);
|
);
|
||||||
HEADER_SEARCH_PATHS = (
|
HEADER_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"$(SRCROOT)/../node_modules/realm/src/**",
|
"$(SRCROOT)/../node_modules/realm/src/**",
|
||||||
"$(SRCROOT)/../node_modules/react-native-share/ios",
|
"$(SRCROOT)/../node_modules/react-native-share/ios",
|
||||||
"$(SRCROOT)/../node_modules/react-native-vector-icons/RNVectorIconsManager",
|
"$(SRCROOT)/../node_modules/react-native-vector-icons/RNVectorIconsManager",
|
||||||
|
"$(SRCROOT)/../node_modules/react-native-document-picker/ios/RNDocumentPicker",
|
||||||
|
"$(SRCROOT)/../node_modules/react-native-fs/**",
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
name = Release;
|
name = Release;
|
||||||
@@ -1329,6 +1347,8 @@
|
|||||||
"$(SRCROOT)/../node_modules/realm/src/**",
|
"$(SRCROOT)/../node_modules/realm/src/**",
|
||||||
"$(SRCROOT)/../node_modules/react-native-share/ios",
|
"$(SRCROOT)/../node_modules/react-native-share/ios",
|
||||||
"$(SRCROOT)/../node_modules/react-native-vector-icons/RNVectorIconsManager",
|
"$(SRCROOT)/../node_modules/react-native-vector-icons/RNVectorIconsManager",
|
||||||
|
"$(SRCROOT)/../node_modules/react-native-document-picker/ios/RNDocumentPicker",
|
||||||
|
"$(SRCROOT)/../node_modules/react-native-fs/**",
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
name = Debug;
|
name = Debug;
|
||||||
@@ -1352,6 +1372,8 @@
|
|||||||
"$(SRCROOT)/../node_modules/realm/src/**",
|
"$(SRCROOT)/../node_modules/realm/src/**",
|
||||||
"$(SRCROOT)/../node_modules/react-native-share/ios",
|
"$(SRCROOT)/../node_modules/react-native-share/ios",
|
||||||
"$(SRCROOT)/../node_modules/react-native-vector-icons/RNVectorIconsManager",
|
"$(SRCROOT)/../node_modules/react-native-vector-icons/RNVectorIconsManager",
|
||||||
|
"$(SRCROOT)/../node_modules/react-native-document-picker/ios/RNDocumentPicker",
|
||||||
|
"$(SRCROOT)/../node_modules/react-native-fs/**",
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
name = Release;
|
name = Release;
|
||||||
@@ -1382,12 +1404,16 @@
|
|||||||
LIBRARY_SEARCH_PATHS = (
|
LIBRARY_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"\"$(SRCROOT)/$(TARGET_NAME)\"",
|
"\"$(SRCROOT)/$(TARGET_NAME)\"",
|
||||||
|
"\"$(SRCROOT)/$(TARGET_NAME)\"",
|
||||||
|
"\"$(SRCROOT)/$(TARGET_NAME)\"",
|
||||||
);
|
);
|
||||||
HEADER_SEARCH_PATHS = (
|
HEADER_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"$(SRCROOT)/../node_modules/realm/src/**",
|
"$(SRCROOT)/../node_modules/realm/src/**",
|
||||||
"$(SRCROOT)/../node_modules/react-native-share/ios",
|
"$(SRCROOT)/../node_modules/react-native-share/ios",
|
||||||
"$(SRCROOT)/../node_modules/react-native-vector-icons/RNVectorIconsManager",
|
"$(SRCROOT)/../node_modules/react-native-vector-icons/RNVectorIconsManager",
|
||||||
|
"$(SRCROOT)/../node_modules/react-native-document-picker/ios/RNDocumentPicker",
|
||||||
|
"$(SRCROOT)/../node_modules/react-native-fs/**",
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
name = Debug;
|
name = Debug;
|
||||||
@@ -1418,12 +1444,16 @@
|
|||||||
LIBRARY_SEARCH_PATHS = (
|
LIBRARY_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"\"$(SRCROOT)/$(TARGET_NAME)\"",
|
"\"$(SRCROOT)/$(TARGET_NAME)\"",
|
||||||
|
"\"$(SRCROOT)/$(TARGET_NAME)\"",
|
||||||
|
"\"$(SRCROOT)/$(TARGET_NAME)\"",
|
||||||
);
|
);
|
||||||
HEADER_SEARCH_PATHS = (
|
HEADER_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"$(SRCROOT)/../node_modules/realm/src/**",
|
"$(SRCROOT)/../node_modules/realm/src/**",
|
||||||
"$(SRCROOT)/../node_modules/react-native-share/ios",
|
"$(SRCROOT)/../node_modules/react-native-share/ios",
|
||||||
"$(SRCROOT)/../node_modules/react-native-vector-icons/RNVectorIconsManager",
|
"$(SRCROOT)/../node_modules/react-native-vector-icons/RNVectorIconsManager",
|
||||||
|
"$(SRCROOT)/../node_modules/react-native-document-picker/ios/RNDocumentPicker",
|
||||||
|
"$(SRCROOT)/../node_modules/react-native-fs/**",
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
name = Release;
|
name = Release;
|
||||||
@@ -1453,12 +1483,16 @@
|
|||||||
LIBRARY_SEARCH_PATHS = (
|
LIBRARY_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"\"$(SRCROOT)/$(TARGET_NAME)\"",
|
"\"$(SRCROOT)/$(TARGET_NAME)\"",
|
||||||
|
"\"$(SRCROOT)/$(TARGET_NAME)\"",
|
||||||
|
"\"$(SRCROOT)/$(TARGET_NAME)\"",
|
||||||
);
|
);
|
||||||
HEADER_SEARCH_PATHS = (
|
HEADER_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"$(SRCROOT)/../node_modules/realm/src/**",
|
"$(SRCROOT)/../node_modules/realm/src/**",
|
||||||
"$(SRCROOT)/../node_modules/react-native-share/ios",
|
"$(SRCROOT)/../node_modules/react-native-share/ios",
|
||||||
"$(SRCROOT)/../node_modules/react-native-vector-icons/RNVectorIconsManager",
|
"$(SRCROOT)/../node_modules/react-native-vector-icons/RNVectorIconsManager",
|
||||||
|
"$(SRCROOT)/../node_modules/react-native-document-picker/ios/RNDocumentPicker",
|
||||||
|
"$(SRCROOT)/../node_modules/react-native-fs/**",
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
name = Debug;
|
name = Debug;
|
||||||
@@ -1488,12 +1522,16 @@
|
|||||||
LIBRARY_SEARCH_PATHS = (
|
LIBRARY_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"\"$(SRCROOT)/$(TARGET_NAME)\"",
|
"\"$(SRCROOT)/$(TARGET_NAME)\"",
|
||||||
|
"\"$(SRCROOT)/$(TARGET_NAME)\"",
|
||||||
|
"\"$(SRCROOT)/$(TARGET_NAME)\"",
|
||||||
);
|
);
|
||||||
HEADER_SEARCH_PATHS = (
|
HEADER_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"$(SRCROOT)/../node_modules/realm/src/**",
|
"$(SRCROOT)/../node_modules/realm/src/**",
|
||||||
"$(SRCROOT)/../node_modules/react-native-share/ios",
|
"$(SRCROOT)/../node_modules/react-native-share/ios",
|
||||||
"$(SRCROOT)/../node_modules/react-native-vector-icons/RNVectorIconsManager",
|
"$(SRCROOT)/../node_modules/react-native-vector-icons/RNVectorIconsManager",
|
||||||
|
"$(SRCROOT)/../node_modules/react-native-document-picker/ios/RNDocumentPicker",
|
||||||
|
"$(SRCROOT)/../node_modules/react-native-fs/**",
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
name = Release;
|
name = Release;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import objectPath from 'object-path'
|
import objectPath from 'object-path'
|
||||||
import { Base64 } from 'js-base64'
|
import { Base64 } from 'js-base64'
|
||||||
|
import { cycleDaysSortedByDate } from '../../db'
|
||||||
import { getColumnNamesForCsv, cycleDaysSortedByDate } from '../db'
|
import getColumnNamesForCsv from './get-csv-column-names'
|
||||||
|
|
||||||
export default function makeDataURI() {
|
export default function makeDataURI() {
|
||||||
if (!cycleDaysSortedByDate.length) return null
|
if (!cycleDaysSortedByDate.length) return null
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import { schema } from '../../db'
|
||||||
|
|
||||||
|
export default function getColumnNamesForCsv() {
|
||||||
|
return getPrefixedKeys('CycleDay')
|
||||||
|
|
||||||
|
function getPrefixedKeys(schemaName, prefix) {
|
||||||
|
const model = schema[schemaName]
|
||||||
|
return Object.keys(model).reduce((acc, key) => {
|
||||||
|
const prefixedKey = prefix ? [prefix, key].join('.') : key
|
||||||
|
const childSchemaName = model[key].objectType
|
||||||
|
if (!childSchemaName) {
|
||||||
|
acc.push(prefixedKey)
|
||||||
|
return acc
|
||||||
|
}
|
||||||
|
acc.push(...getPrefixedKeys(childSchemaName, prefixedKey))
|
||||||
|
return acc
|
||||||
|
}, [])
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
import csvParser from 'csvtojson'
|
||||||
|
import isObject from 'isobject'
|
||||||
|
import { schema, tryToImportWithDelete, tryToImportWithoutDelete } from '../../db'
|
||||||
|
import getColumnNamesForCsv from './get-csv-column-names'
|
||||||
|
|
||||||
|
export default async function importCsv(csv, deleteFirst) {
|
||||||
|
const parseFuncs = {
|
||||||
|
bool: val => {
|
||||||
|
if (val.toLowerCase() === 'true') return true
|
||||||
|
if (val.toLowerCase() === 'false') return false
|
||||||
|
return val
|
||||||
|
},
|
||||||
|
int: parseNumberIfPossible,
|
||||||
|
float: parseNumberIfPossible,
|
||||||
|
double: parseNumberIfPossible,
|
||||||
|
string: val => val
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseNumberIfPossible(val) {
|
||||||
|
// Number and parseFloat catch different cases of weirdness,
|
||||||
|
// so we test them both
|
||||||
|
if (isNaN(Number(val)) || isNaN(parseFloat(val))) return val
|
||||||
|
return Number(val)
|
||||||
|
}
|
||||||
|
|
||||||
|
const config = {
|
||||||
|
ignoreEmpty: true,
|
||||||
|
colParser: getColumnNamesForCsv().reduce((acc, colName) => {
|
||||||
|
const path = colName.split('.')
|
||||||
|
const dbType = getDbType(schema.CycleDay, path)
|
||||||
|
acc[colName] = item => {
|
||||||
|
if (item === '') return null
|
||||||
|
return parseFuncs[dbType](item)
|
||||||
|
}
|
||||||
|
return acc
|
||||||
|
}, {})
|
||||||
|
}
|
||||||
|
|
||||||
|
const cycleDays = await csvParser(config)
|
||||||
|
.fromString(csv)
|
||||||
|
.on('header', validateHeaders)
|
||||||
|
|
||||||
|
//remove symptoms where all fields are null
|
||||||
|
putNullForEmptySymptoms(cycleDays)
|
||||||
|
|
||||||
|
if (deleteFirst) {
|
||||||
|
tryToImportWithDelete(cycleDays)
|
||||||
|
} else {
|
||||||
|
tryToImportWithoutDelete(cycleDays)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function validateHeaders(headers) {
|
||||||
|
const expectedHeaders = getColumnNamesForCsv()
|
||||||
|
if (!headers.every(header => {
|
||||||
|
return expectedHeaders.indexOf(header) > -1
|
||||||
|
})) {
|
||||||
|
const msg = `Expected CSV column titles to be ${expectedHeaders.join()}`
|
||||||
|
throw new Error(msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function putNullForEmptySymptoms(data) {
|
||||||
|
data.forEach(replaceWithNullIfAllPropertiesAreNull)
|
||||||
|
|
||||||
|
function replaceWithNullIfAllPropertiesAreNull(obj) {
|
||||||
|
Object.keys(obj).forEach((key) => {
|
||||||
|
if (!isObject(obj[key])) return
|
||||||
|
if (Object.values(obj[key]).every(val => val === null)) {
|
||||||
|
obj[key] = null
|
||||||
|
return
|
||||||
|
}
|
||||||
|
replaceWithNullIfAllPropertiesAreNull(obj[key])
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDbType(modelProperties, path) {
|
||||||
|
if (path.length === 1) return modelProperties[path[0]].type
|
||||||
|
const modelName = modelProperties[path[0]].objectType
|
||||||
|
return getDbType(schema[modelName], path.slice(1))
|
||||||
|
}
|
||||||
Generated
+67
-6
@@ -2015,6 +2015,11 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"base-64": {
|
||||||
|
"version": "0.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/base-64/-/base-64-0.1.0.tgz",
|
||||||
|
"integrity": "sha1-eAqZyE59YAJgNhURxId2E78k9rs="
|
||||||
|
},
|
||||||
"base64-js": {
|
"base64-js": {
|
||||||
"version": "1.3.0",
|
"version": "1.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.0.tgz",
|
||||||
@@ -2066,6 +2071,11 @@
|
|||||||
"inherits": "~2.0.0"
|
"inherits": "~2.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"bluebird": {
|
||||||
|
"version": "3.5.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.1.tgz",
|
||||||
|
"integrity": "sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA=="
|
||||||
|
},
|
||||||
"boom": {
|
"boom": {
|
||||||
"version": "2.10.1",
|
"version": "2.10.1",
|
||||||
"resolved": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz",
|
"resolved": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz",
|
||||||
@@ -2545,6 +2555,26 @@
|
|||||||
"boom": "2.x.x"
|
"boom": "2.x.x"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"csvtojson": {
|
||||||
|
"version": "2.0.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/csvtojson/-/csvtojson-2.0.8.tgz",
|
||||||
|
"integrity": "sha512-DC6YFtsJiA7t/Yz+KjzT6GXuKtU/5gRbbl7HJqvDVVir+dxdw2/1EgwfgJdnsvUT7lOnON5DvGftKuYWX1nMOQ==",
|
||||||
|
"requires": {
|
||||||
|
"bluebird": "^3.5.1",
|
||||||
|
"lodash": "^4.17.3",
|
||||||
|
"strip-bom": "^2.0.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"strip-bom": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz",
|
||||||
|
"integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=",
|
||||||
|
"requires": {
|
||||||
|
"is-utf8": "^0.2.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"dashdash": {
|
"dashdash": {
|
||||||
"version": "1.14.1",
|
"version": "1.14.1",
|
||||||
"resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
|
"resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
|
||||||
@@ -3341,6 +3371,16 @@
|
|||||||
"randomatic": "^3.0.0",
|
"randomatic": "^3.0.0",
|
||||||
"repeat-element": "^1.1.2",
|
"repeat-element": "^1.1.2",
|
||||||
"repeat-string": "^1.5.2"
|
"repeat-string": "^1.5.2"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"isobject": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz",
|
||||||
|
"integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=",
|
||||||
|
"requires": {
|
||||||
|
"isarray": "1.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"finalhandler": {
|
"finalhandler": {
|
||||||
@@ -4657,6 +4697,11 @@
|
|||||||
"resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
|
||||||
"integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo="
|
"integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo="
|
||||||
},
|
},
|
||||||
|
"is-utf8": {
|
||||||
|
"version": "0.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz",
|
||||||
|
"integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI="
|
||||||
|
},
|
||||||
"is-windows": {
|
"is-windows": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz",
|
||||||
@@ -4673,12 +4718,9 @@
|
|||||||
"integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA="
|
"integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA="
|
||||||
},
|
},
|
||||||
"isobject": {
|
"isobject": {
|
||||||
"version": "2.1.0",
|
"version": "3.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
|
||||||
"integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=",
|
"integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8="
|
||||||
"requires": {
|
|
||||||
"isarray": "1.0.0"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"isomorphic-fetch": {
|
"isomorphic-fetch": {
|
||||||
"version": "2.2.1",
|
"version": "2.2.1",
|
||||||
@@ -6351,6 +6393,11 @@
|
|||||||
"resolved": "https://registry.npmjs.org/react-native-dismiss-keyboard/-/react-native-dismiss-keyboard-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-native-dismiss-keyboard/-/react-native-dismiss-keyboard-1.0.0.tgz",
|
||||||
"integrity": "sha1-MohiQrPyMX4SHzrrmwpYXiuHm0k="
|
"integrity": "sha1-MohiQrPyMX4SHzrrmwpYXiuHm0k="
|
||||||
},
|
},
|
||||||
|
"react-native-document-picker": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-native-document-picker/-/react-native-document-picker-2.1.0.tgz",
|
||||||
|
"integrity": "sha512-BFCBXwz8xuLvHLVFVeQM+RhaY8yZ38PEWt9WSbq5VIoZ/VssP6uu51XxOfdwaMALOrAHIojK0SiYnd155upZAg=="
|
||||||
|
},
|
||||||
"react-native-drawer-layout": {
|
"react-native-drawer-layout": {
|
||||||
"version": "1.3.2",
|
"version": "1.3.2",
|
||||||
"resolved": "https://registry.npmjs.org/react-native-drawer-layout/-/react-native-drawer-layout-1.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/react-native-drawer-layout/-/react-native-drawer-layout-1.3.2.tgz",
|
||||||
@@ -6367,6 +6414,15 @@
|
|||||||
"react-native-drawer-layout": "1.3.2"
|
"react-native-drawer-layout": "1.3.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"react-native-fs": {
|
||||||
|
"version": "2.10.14",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-native-fs/-/react-native-fs-2.10.14.tgz",
|
||||||
|
"integrity": "sha512-4bCzkg4dE/xUyXkMVz0AiyqLKAgTZPlZl/nEzRiSr2q6VnWDgO229MSgHLHhUtD2cqZkV0Z83WEbGpvXxWOAHA==",
|
||||||
|
"requires": {
|
||||||
|
"base-64": "^0.1.0",
|
||||||
|
"utf8": "^2.1.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"react-native-modal": {
|
"react-native-modal": {
|
||||||
"version": "3.1.0",
|
"version": "3.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/react-native-modal/-/react-native-modal-3.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-native-modal/-/react-native-modal-3.1.0.tgz",
|
||||||
@@ -8323,6 +8379,11 @@
|
|||||||
"resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz",
|
||||||
"integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ=="
|
"integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ=="
|
||||||
},
|
},
|
||||||
|
"utf8": {
|
||||||
|
"version": "2.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/utf8/-/utf8-2.1.2.tgz",
|
||||||
|
"integrity": "sha1-H6DZJw6b6FDZsFAn9jUZv0ZFfZY="
|
||||||
|
},
|
||||||
"util": {
|
"util": {
|
||||||
"version": "0.10.3",
|
"version": "0.10.3",
|
||||||
"resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz",
|
"resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz",
|
||||||
|
|||||||
@@ -16,7 +16,9 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"assert": "^1.4.1",
|
"assert": "^1.4.1",
|
||||||
|
"csvtojson": "^2.0.8",
|
||||||
"date-range": "0.0.2",
|
"date-range": "0.0.2",
|
||||||
|
"isobject": "^3.0.1",
|
||||||
"js-base64": "^2.4.8",
|
"js-base64": "^2.4.8",
|
||||||
"js-joda": "^1.8.2",
|
"js-joda": "^1.8.2",
|
||||||
"moment": "^2.22.1",
|
"moment": "^2.22.1",
|
||||||
@@ -24,6 +26,8 @@
|
|||||||
"react": "16.4.1",
|
"react": "16.4.1",
|
||||||
"react-native": "^0.56.0",
|
"react-native": "^0.56.0",
|
||||||
"react-native-calendars": "^1.19.3",
|
"react-native-calendars": "^1.19.3",
|
||||||
|
"react-native-document-picker": "^2.1.0",
|
||||||
|
"react-native-fs": "^2.10.14",
|
||||||
"react-native-modal-datetime-picker-nevo": "^4.11.0",
|
"react-native-modal-datetime-picker-nevo": "^4.11.0",
|
||||||
"react-native-share": "^1.1.0",
|
"react-native-share": "^1.1.0",
|
||||||
"react-native-simple-radio-button": "^2.7.1",
|
"react-native-simple-radio-button": "^2.7.1",
|
||||||
|
|||||||
Reference in New Issue
Block a user