553 lines
14 KiB
JavaScript
553 lines
14 KiB
JavaScript
import { ChronoUnit, LocalDate, LocalTime } from '@js-joda/core'
|
|
|
|
import {
|
|
getPreviousTemperatureForDate,
|
|
saveSymptom,
|
|
mapRealmObjToJsObj,
|
|
} from '../../db'
|
|
import { scaleObservable } from '../../local-storage'
|
|
|
|
import * as labels from '../../i18n/en/cycle-day'
|
|
import { getLabelsList } from './labels'
|
|
import { TEMP_MAX, TEMP_MIN } from '../../config'
|
|
|
|
import computeNfpValue from '../../lib/nfp-mucus'
|
|
|
|
const bleedingLabels = labels.bleeding.labels
|
|
const cervixLabels = labels.cervix
|
|
const contraceptiveLabels = labels.contraceptives.categories
|
|
const intensityLabels = labels.intensity
|
|
const moodLabels = labels.mood.categories
|
|
const mucusLabels = labels.mucus
|
|
const noteDescription = labels.noteExplainer
|
|
const painLabels = labels.pain.categories
|
|
const sexLabels = labels.sex.categories
|
|
const temperatureLabels = labels.temperature
|
|
const productLabels = labels.products.categories
|
|
|
|
const minutes = ChronoUnit.MINUTES
|
|
|
|
const isNumber = (value) => typeof value === 'number'
|
|
export const shouldShow = (value) => (value !== null ? true : false)
|
|
|
|
export const formatTemperature = (temperature) =>
|
|
!temperature
|
|
? temperature
|
|
: Number.parseFloat(temperature.toString()).toFixed(2)
|
|
|
|
//maximum of precision digits after decimal point, but no x.0
|
|
export const formatDecimal = (num, precision) =>
|
|
+parseFloat(Number.parseFloat(num).toFixed(precision))
|
|
|
|
export const getPreviousTemperature = (date) => {
|
|
const previousTemperature = getPreviousTemperatureForDate(date)
|
|
return formatTemperature(previousTemperature)
|
|
}
|
|
|
|
export const getTemperatureOutOfRangeMessage = (temperature) => {
|
|
if (!temperature) return null
|
|
|
|
const value = Number(temperature)
|
|
const scale = scaleObservable.value
|
|
|
|
return value < TEMP_MIN || value > TEMP_MAX
|
|
? labels.temperature.outOfAbsoluteRangeWarning
|
|
: value < scale.min || value > scale.max
|
|
? labels.temperature.outOfRangeWarning
|
|
: ''
|
|
}
|
|
|
|
export const blank = {
|
|
bleeding: {
|
|
exclude: false,
|
|
value: null,
|
|
pad: null,
|
|
tampon: null,
|
|
underwear: null,
|
|
cup: null,
|
|
softTampon: null,
|
|
disk: null,
|
|
none: null,
|
|
other: null,
|
|
note: null,
|
|
},
|
|
cervix: {
|
|
exclude: false,
|
|
firmness: null,
|
|
opening: null,
|
|
position: null,
|
|
},
|
|
desire: {
|
|
value: null,
|
|
},
|
|
mood: {
|
|
happy: null,
|
|
sad: null,
|
|
stressed: null,
|
|
balanced: null,
|
|
fine: null,
|
|
anxious: null,
|
|
energetic: null,
|
|
fatigue: null,
|
|
angry: null,
|
|
other: null,
|
|
note: null,
|
|
},
|
|
mucus: {
|
|
exclude: false,
|
|
feeling: null,
|
|
texture: null,
|
|
value: null,
|
|
},
|
|
note: {
|
|
value: null,
|
|
},
|
|
pain: {
|
|
cramps: null,
|
|
ovulationPain: null,
|
|
headache: null,
|
|
backache: null,
|
|
nausea: null,
|
|
tenderBreasts: null,
|
|
migraine: null,
|
|
other: null,
|
|
note: null,
|
|
},
|
|
sex: {
|
|
solo: null,
|
|
partner: null,
|
|
condom: null,
|
|
pill: null,
|
|
iud: null,
|
|
patch: null,
|
|
ring: null,
|
|
implant: null,
|
|
diaphragm: null,
|
|
none: null,
|
|
other: null,
|
|
note: null,
|
|
},
|
|
temperature: {
|
|
exclude: false,
|
|
note: null,
|
|
time: LocalTime.now().truncatedTo(minutes).toString(),
|
|
value: null,
|
|
},
|
|
}
|
|
|
|
export const symptomPage = {
|
|
bleeding: {
|
|
excludeText: labels.bleeding.exclude.explainer,
|
|
note: null,
|
|
selectTabGroups: [
|
|
{
|
|
key: 'value',
|
|
options: getLabelsList(bleedingLabels),
|
|
title: labels.bleeding.heaviness.explainer,
|
|
},
|
|
],
|
|
selectBoxGroups: [
|
|
{
|
|
key: 'products',
|
|
options: productLabels,
|
|
title: labels.products.explainer,
|
|
},
|
|
],
|
|
},
|
|
cervix: {
|
|
excludeText: cervixLabels.excludeExplainer,
|
|
note: null,
|
|
selectBoxGroups: null,
|
|
selectTabGroups: [
|
|
{
|
|
key: 'opening',
|
|
options: getLabelsList(cervixLabels.opening.categories),
|
|
title: cervixLabels.opening.explainer,
|
|
},
|
|
{
|
|
key: 'firmness',
|
|
options: getLabelsList(cervixLabels.firmness.categories),
|
|
title: cervixLabels.firmness.explainer,
|
|
},
|
|
{
|
|
key: 'position',
|
|
options: getLabelsList(cervixLabels.position.categories),
|
|
title: cervixLabels.position.explainer,
|
|
},
|
|
],
|
|
},
|
|
desire: {
|
|
excludeText: null,
|
|
note: null,
|
|
selectBoxGroups: null,
|
|
selectTabGroups: [
|
|
{
|
|
key: 'value',
|
|
options: getLabelsList(intensityLabels),
|
|
title: labels.desire.explainer,
|
|
},
|
|
],
|
|
},
|
|
mucus: {
|
|
excludeText: mucusLabels.excludeExplainer,
|
|
note: null,
|
|
selectBoxGroups: null,
|
|
selectTabGroups: [
|
|
{
|
|
key: 'feeling',
|
|
options: getLabelsList(mucusLabels.feeling.categories),
|
|
title: mucusLabels.feeling.explainer,
|
|
},
|
|
{
|
|
key: 'texture',
|
|
options: getLabelsList(mucusLabels.texture.categories),
|
|
title: mucusLabels.texture.explainer,
|
|
},
|
|
],
|
|
},
|
|
mood: {
|
|
excludeText: null,
|
|
note: null,
|
|
selectBoxGroups: [
|
|
{
|
|
key: 'mood',
|
|
options: moodLabels,
|
|
title: labels.mood.explainer,
|
|
},
|
|
],
|
|
selectTabGroups: null,
|
|
},
|
|
note: {
|
|
excludeText: null,
|
|
note: noteDescription,
|
|
selectBoxGroups: null,
|
|
selectTabGroups: null,
|
|
},
|
|
pain: {
|
|
excludeText: null,
|
|
note: null,
|
|
selectBoxGroups: [
|
|
{
|
|
key: 'pain',
|
|
options: painLabels,
|
|
title: labels.pain.explainer,
|
|
},
|
|
],
|
|
selectTabGroups: null,
|
|
},
|
|
sex: {
|
|
excludeText: null,
|
|
note: null,
|
|
selectBoxGroups: [
|
|
{
|
|
key: 'sex',
|
|
options: sexLabels,
|
|
title: labels.sex.explainer,
|
|
},
|
|
{
|
|
key: 'contraceptives',
|
|
options: contraceptiveLabels,
|
|
title: labels.contraceptives.explainer,
|
|
},
|
|
],
|
|
selectTabGroups: null,
|
|
},
|
|
temperature: {
|
|
excludeText: temperatureLabels.exclude.explainer,
|
|
note: temperatureLabels.note.explainer,
|
|
selectBoxGroups: null,
|
|
selectTabGroups: null,
|
|
},
|
|
}
|
|
|
|
export const save = {
|
|
bleeding: (data, date, shouldDeleteData) => {
|
|
const {
|
|
value,
|
|
pad,
|
|
tampon,
|
|
underwear,
|
|
cup,
|
|
softTampon,
|
|
disk,
|
|
none,
|
|
other,
|
|
note,
|
|
} = data
|
|
let { exclude } = data
|
|
const isDataEntered = [
|
|
'value',
|
|
'pad',
|
|
'tampon',
|
|
'underwear',
|
|
'cup',
|
|
'softTampon',
|
|
'disk',
|
|
'none',
|
|
'other',
|
|
'note',
|
|
].some((value) => typeof data[value] === 'number' || data[value] == true)
|
|
// covering the case when a bleeding value was entered, "excluded" and saved. But then the bleeding value was deleted again.
|
|
exclude = isNumber(value) ? exclude : false
|
|
const valuesToSave =
|
|
shouldDeleteData || !isDataEntered
|
|
? null
|
|
: {
|
|
exclude,
|
|
value,
|
|
pad,
|
|
tampon,
|
|
underwear,
|
|
cup,
|
|
softTampon,
|
|
disk,
|
|
none,
|
|
other,
|
|
note,
|
|
}
|
|
|
|
saveSymptom('bleeding', date, valuesToSave)
|
|
},
|
|
cervix: (data, date, shouldDeleteData) => {
|
|
const { opening, firmness, position, exclude } = data
|
|
const isDataEntered = ['opening', 'firmness', 'position'].some((value) =>
|
|
isNumber(data[value])
|
|
)
|
|
const valuesToSave =
|
|
shouldDeleteData || !isDataEntered
|
|
? null
|
|
: { opening, firmness, position, exclude }
|
|
|
|
saveSymptom('cervix', date, valuesToSave)
|
|
},
|
|
desire: (data, date, shouldDeleteData) => {
|
|
const { value } = data
|
|
const valuesToSave = shouldDeleteData || !isNumber(value) ? null : { value }
|
|
|
|
saveSymptom('desire', date, valuesToSave)
|
|
},
|
|
mood: (data, date, shouldDeleteData) => {
|
|
saveBoxSymptom(data, date, shouldDeleteData, 'mood')
|
|
},
|
|
mucus: (data, date, shouldDeleteData) => {
|
|
const { feeling, texture, exclude } = data
|
|
const isDataEntered = ['feeling', 'texture'].some((value) =>
|
|
isNumber(data[value])
|
|
)
|
|
const valuesToSave =
|
|
shouldDeleteData || !isDataEntered
|
|
? null
|
|
: {
|
|
feeling,
|
|
texture,
|
|
value: computeNfpValue(feeling, texture),
|
|
exclude,
|
|
}
|
|
|
|
saveSymptom('mucus', date, valuesToSave)
|
|
},
|
|
note: (data, date, shouldDeleteData) => {
|
|
const { value } = data
|
|
const isValidData = value !== null && value !== ''
|
|
const valuesToSave = shouldDeleteData || !isValidData ? null : { value }
|
|
|
|
saveSymptom('note', date, valuesToSave)
|
|
},
|
|
pain: (data, date, shouldDeleteData) => {
|
|
saveBoxSymptom(data, date, shouldDeleteData, 'pain')
|
|
},
|
|
sex: (data, date, shouldDeleteData) => {
|
|
saveBoxSymptom(data, date, shouldDeleteData, 'sex')
|
|
},
|
|
temperature: (data, date, shouldDeleteData) => {
|
|
const { exclude, note, time, value } = data
|
|
const valuesToSave = {
|
|
exclude,
|
|
note,
|
|
time,
|
|
value: Number(value),
|
|
}
|
|
|
|
saveSymptom(
|
|
'temperature',
|
|
date,
|
|
shouldDeleteData || value === null ? null : valuesToSave
|
|
)
|
|
},
|
|
}
|
|
|
|
const saveBoxSymptom = (data, date, shouldDeleteData, symptom) => {
|
|
const isDataEntered = Object.keys(data).some((key) => data[key] !== null)
|
|
const valuesToSave = shouldDeleteData || !isDataEntered ? null : data
|
|
|
|
saveSymptom(symptom, date, valuesToSave)
|
|
}
|
|
|
|
const label = {
|
|
bleeding: (bleeding) => {
|
|
bleeding = mapRealmObjToJsObj(bleeding)
|
|
const bleedingLabel = []
|
|
if (
|
|
bleeding &&
|
|
Object.values({ ...bleeding }).some(
|
|
(val) => typeof val === 'number' || val == true
|
|
)
|
|
) {
|
|
Object.keys(bleeding).forEach((key) => {
|
|
if (bleeding[key] != null && key === 'value') {
|
|
bleedingLabel.push(
|
|
bleeding.exclude
|
|
? `(${bleedingLabels[bleeding[key]]})`
|
|
: bleedingLabels[bleeding[key]]
|
|
)
|
|
}
|
|
if (
|
|
bleeding[key] &&
|
|
key !== 'other' &&
|
|
key !== 'note' &&
|
|
key !== 'value' &&
|
|
key !== 'exclude'
|
|
) {
|
|
bleedingLabel.push(bleedingLabels[key] || productLabels[key])
|
|
}
|
|
if (key === 'other' && bleeding.other) {
|
|
let label = productLabels[key]
|
|
if (bleeding.note) {
|
|
label = `${label} (${bleeding.note})`
|
|
}
|
|
bleedingLabel.push(label)
|
|
}
|
|
})
|
|
return bleedingLabel.join(', ')
|
|
}
|
|
},
|
|
temperature: ({ value, time, exclude }) => {
|
|
if (isNumber(value)) {
|
|
let temperatureLabel = `${value} °C`
|
|
if (time) {
|
|
temperatureLabel += ` - ${time}`
|
|
}
|
|
if (exclude) {
|
|
temperatureLabel = `(${temperatureLabel})`
|
|
}
|
|
return temperatureLabel
|
|
}
|
|
},
|
|
mucus: (mucus) => {
|
|
const filledCategories = ['feeling', 'texture'].filter((c) =>
|
|
isNumber(mucus[c])
|
|
)
|
|
let label = filledCategories
|
|
.map((category) => {
|
|
return (
|
|
labels.mucus.subcategories[category] +
|
|
': ' +
|
|
labels.mucus[category].categories[mucus[category]]
|
|
)
|
|
})
|
|
.join(', ')
|
|
|
|
if (isNumber(mucus.value)) label += ` => ${labels.mucusNFP[mucus.value]}`
|
|
if (mucus.exclude) label = `(${label})`
|
|
|
|
return label
|
|
},
|
|
cervix: (cervix) => {
|
|
const filledCategories = ['opening', 'firmness', 'position'].filter((c) =>
|
|
isNumber(cervix[c])
|
|
)
|
|
let label = filledCategories
|
|
.map((category) => {
|
|
return (
|
|
labels.cervix.subcategories[category] +
|
|
': ' +
|
|
labels.cervix[category].categories[cervix[category]]
|
|
)
|
|
})
|
|
.join(', ')
|
|
|
|
if (cervix.exclude) label = `(${label})`
|
|
|
|
return label
|
|
},
|
|
note: (note) => note.value,
|
|
desire: ({ value }) => {
|
|
if (isNumber(value)) {
|
|
return intensityLabels[value]
|
|
}
|
|
},
|
|
sex: (sex) => {
|
|
sex = mapRealmObjToJsObj(sex)
|
|
const sexLabel = []
|
|
if (sex && Object.values({ ...sex }).some((val) => val)) {
|
|
Object.keys(sex).forEach((key) => {
|
|
if (sex[key] && key !== 'other' && key !== 'note') {
|
|
sexLabel.push(sexLabels[key] || contraceptiveLabels[key])
|
|
}
|
|
if (key === 'other' && sex.other) {
|
|
let label = contraceptiveLabels[key]
|
|
if (sex.note) {
|
|
label = `${label} (${sex.note})`
|
|
}
|
|
sexLabel.push(label)
|
|
}
|
|
})
|
|
return sexLabel.join(', ')
|
|
}
|
|
},
|
|
pain: (pain) => {
|
|
pain = mapRealmObjToJsObj(pain)
|
|
const painLabel = []
|
|
if (pain && Object.values({ ...pain }).some((val) => val)) {
|
|
Object.keys(pain).forEach((key) => {
|
|
if (pain[key] && key !== 'other' && key !== 'note') {
|
|
painLabel.push(painLabels[key])
|
|
}
|
|
if (key === 'other' && pain.other) {
|
|
let label = painLabels[key]
|
|
if (pain.note) {
|
|
label = `${label} (${pain.note})`
|
|
}
|
|
painLabel.push(label)
|
|
}
|
|
})
|
|
return painLabel.join(', ')
|
|
}
|
|
},
|
|
mood: (mood) => {
|
|
mood = mapRealmObjToJsObj(mood)
|
|
const moodLabel = []
|
|
if (mood && Object.values({ ...mood }).some((val) => val)) {
|
|
Object.keys(mood).forEach((key) => {
|
|
if (mood[key] && key !== 'other' && key !== 'note') {
|
|
moodLabel.push(moodLabels[key])
|
|
}
|
|
if (key === 'other' && mood.other) {
|
|
let label = moodLabels[key]
|
|
if (mood.note) {
|
|
label = `${label} (${mood.note})`
|
|
}
|
|
moodLabel.push(label)
|
|
}
|
|
})
|
|
return moodLabel.join(', ')
|
|
}
|
|
},
|
|
}
|
|
|
|
export const getData = (symptom, symptomData) => {
|
|
return symptomData && label[symptom](symptomData)
|
|
}
|
|
|
|
export const prevDate = (dateString) => {
|
|
return LocalDate.parse(dateString).minusDays(1).toString()
|
|
}
|
|
|
|
export const nextDate = (dateString) => {
|
|
return LocalDate.parse(dateString).plusDays(1).toString()
|
|
}
|
|
|
|
export const isDateInFuture = (dateString) => {
|
|
return LocalDate.now().isBefore(LocalDate.parse(dateString))
|
|
}
|